Spring Cloud Kubernetes Reload
This commit is contained in:
committed by
Ioannis Canellos
parent
7e521f5c23
commit
41bb31fd4c
58
example/pom.xml
Normal file
58
example/pom.xml
Normal file
@@ -0,0 +1,58 @@
|
||||
<?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">
|
||||
<parent>
|
||||
<artifactId>spring-cloud-kubernetes-project</artifactId>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<version>0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>example</artifactId>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>spring-cloud-kubernetes-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>org.springframework.cloud</groupId>-->
|
||||
<!--<artifactId>spring-cloud-config-client</artifactId>-->
|
||||
<!--</dependency>-->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
<version>1.4.1.RELEASE</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>5.2.4.Final</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>fabric8-maven-plugin</artifactId>
|
||||
<version>3.1.69</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>resource</goal>
|
||||
<goal>build</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
16
example/src/main/java/io/app/App.java
Normal file
16
example/src/main/java/io/app/App.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package io.app;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class App {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(App.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
29
example/src/main/java/io/app/MyBean.java
Normal file
29
example/src/main/java/io/app/MyBean.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package io.app;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "bean")
|
||||
public class MyBean {
|
||||
|
||||
private String message;
|
||||
|
||||
@Scheduled(fixedDelay = 5000)
|
||||
public void hello() {
|
||||
System.out.println("The message is: " + message);
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
}
|
||||
9
example/src/main/resources/application.properties
Normal file
9
example/src/main/resources/application.properties
Normal file
@@ -0,0 +1,9 @@
|
||||
spring.application.name=example
|
||||
|
||||
|
||||
#spring.cloud.kubernetes.reload.strategy=refresh
|
||||
#spring.cloud.kubernetes.reload.mode=polling
|
||||
#spring.cloud.kubernetes.reload.period=2000
|
||||
#spring.cloud.kubernetes.reload.monitoring-config-maps=false
|
||||
#spring.cloud.kubernetes.reload.monitoring-secrets=true
|
||||
#spring.cloud.kubernetes.reload.enabled=true
|
||||
1
pom.xml
1
pom.xml
@@ -106,6 +106,7 @@
|
||||
<module>spring-cloud-starter-kubernetes-netflix</module>
|
||||
<module>spring-cloud-starter-kubernetes-zipkin</module>
|
||||
<module>spring-cloud-starter-kubernetes-all</module>
|
||||
<module>example</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
|
||||
23
readme.md
23
readme.md
@@ -64,7 +64,7 @@ The `ConfigMap` `PropertySource` when enabled will lookup Kubernetes for a `Conf
|
||||
|
||||
- apply individual configuration properties.
|
||||
- apply as yaml the content of any property named `application.yaml`
|
||||
- apple as properties file the content of any property named `application.properties`
|
||||
- apply as properties file the content of any property named `application.properties`
|
||||
|
||||
Example:
|
||||
|
||||
@@ -196,6 +196,27 @@ You can select the Secrets to consume in a number of ways:
|
||||
- The property spring.cloud.kubernetes.secrets.paths behave as defined by [Collection-based binding](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-Configuration-Binding#collection-based-binding).
|
||||
- Access to secrets via API may be restricted for security reasons, the preferred way is to mount secret to the POD.
|
||||
|
||||
#### PropertySource Reload
|
||||
|
||||
Some applications may need to detect changes on external property sources and update their internal status to reflect the new configuration.
|
||||
The reload feature of Spring Cloud Kubernetes is able to trigger an application reload when its related ConfigMap or Secret change.
|
||||
|
||||
This feature is disabled by default and can be enabled using the configuration property `spring.cloud.kubernetes.reload.enabled=true`
|
||||
(eg. in the *application.properties* file).
|
||||
|
||||
The following levels of reload are supported (property `spring.cloud.kubernetes.reload.strategy`):
|
||||
- **refresh (default)**: only spring beans annotated with `@ConfigurationProperties` or `@RefreshScope` are reloaded.
|
||||
This reload level leverages the refresh feature of Spring Cloud Context.
|
||||
- **restart_context**: the whole Spring _ApplicationContext_ is gracefully restarted. Beans are recreated with the new configuration.
|
||||
- **shutdown**: the Spring _ApplicationContext_ is shut down to activate a restart of the container.
|
||||
When using this level, make sure that the lifecycle of all non-daemon threads is bound to the ApplicationContext
|
||||
and that a replication controller or replica set is configured to restart the pod.
|
||||
|
||||
Example:
|
||||
|
||||
Assuming that the reload feature is enabled with default settings, the following bean will be refreshed
|
||||
|
||||
|
||||
### Pod Health Indicator
|
||||
|
||||
Spring Boot uses [HealthIndicator](https://github.com/spring-projects/spring-boot/blob/master/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicator.java) to expose info about the health of an application.
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
|
||||
@Order(0)
|
||||
@@ -35,7 +36,7 @@ public class ConfigMapPropertySourceLocator implements PropertySourceLocator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropertySource<?> locate(Environment environment) {
|
||||
public MapPropertySource locate(Environment environment) {
|
||||
if (environment instanceof ConfigurableEnvironment) {
|
||||
ConfigurableEnvironment env = (ConfigurableEnvironment) environment;
|
||||
String appName = env.getProperty(Constants.SPRING_APPLICATION_NAME, Constants.FALLBACK_APPLICATION_NAME);
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
|
||||
@Order(1)
|
||||
@@ -35,7 +36,7 @@ public class SecretsPropertySourceLocator implements PropertySourceLocator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropertySource<?> locate(Environment environment) {
|
||||
public MapPropertySource locate(Environment environment) {
|
||||
return environment instanceof ConfigurableEnvironment
|
||||
? new SecretsPropertySource(client, environment, properties)
|
||||
: null;
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package io.fabric8.spring.cloud.kubernetes.reload;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.Watch;
|
||||
import io.fabric8.spring.cloud.kubernetes.config.ConfigMapPropertySourceLocator;
|
||||
import io.fabric8.spring.cloud.kubernetes.config.SecretsPropertySourceLocator;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.context.refresh.ContextRefresher;
|
||||
import org.springframework.cloud.context.restart.RestartEndpoint;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.AbstractEnvironment;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Definition of beans needed for the automatic reload of configuration.
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(value = "spring.cloud.kubernetes.enabled", matchIfMissing = true)
|
||||
@EnableConfigurationProperties(ConfigReloadProperties.class)
|
||||
public class ConfigReloadAutoConfiguration {
|
||||
|
||||
/**
|
||||
* Configuration reload must be enabled explicitly.
|
||||
*/
|
||||
@ConditionalOnProperty(value = "spring.cloud.kubernetes.reload.enabled")
|
||||
@ConditionalOnClass({RestartEndpoint.class, ContextRefresher.class})
|
||||
@EnableScheduling
|
||||
@EnableAsync
|
||||
protected static class ConfigReloadAutoConfigurationBeans {
|
||||
|
||||
@Autowired
|
||||
private AbstractEnvironment environment;
|
||||
|
||||
@Autowired
|
||||
private KubernetesClient kubernetesClient;
|
||||
|
||||
@Autowired
|
||||
private ConfigMapPropertySourceLocator configMapPropertySourceLocator;
|
||||
|
||||
@Autowired
|
||||
private SecretsPropertySourceLocator secretsPropertySourceLocator;
|
||||
|
||||
/**
|
||||
* Provides a bean that listen to configuration changes and fire a reload.
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ConfigurationChangeDetector propertyChangeWatcher(ConfigReloadProperties properties, ConfigurationUpdateStrategy strategy, EventWatcher eventWatcher) {
|
||||
switch (properties.getMode()) {
|
||||
case POLLING:
|
||||
return new PollingConfigurationChangeDetector(environment, properties, kubernetesClient, strategy, configMapPropertySourceLocator, secretsPropertySourceLocator);
|
||||
case EVENT:
|
||||
return new EventBasedConfigurationChangeDetector(environment, properties, kubernetesClient, strategy, configMapPropertySourceLocator, secretsPropertySourceLocator, eventWatcher);
|
||||
}
|
||||
throw new IllegalStateException("Unsupported configuration reload mode: " + properties.getMode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the action to execute when the configuration changes.
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ConfigurationUpdateStrategy configurationUpdateStrategy(ConfigReloadProperties properties, ConfigurableApplicationContext ctx, RestartEndpoint restarter, ContextRefresher refresher) {
|
||||
switch (properties.getStrategy()) {
|
||||
case RESTART_CONTEXT:
|
||||
return new ConfigurationUpdateStrategy(properties.getStrategy().name(), restarter::restart);
|
||||
case REFRESH:
|
||||
return new ConfigurationUpdateStrategy(properties.getStrategy().name(), refresher::refresh);
|
||||
case SHUTDOWN:
|
||||
return new ConfigurationUpdateStrategy(properties.getStrategy().name(), ctx::close);
|
||||
}
|
||||
throw new IllegalStateException("Unsupported configuration update strategy: " + properties.getStrategy());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Manages watches asynchronously and clean them up on context close.
|
||||
*/
|
||||
@Component
|
||||
public static class DefaultEventWatcher implements EventWatcher {
|
||||
private Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private KubernetesClient kubernetesClient;
|
||||
|
||||
private Map<String, Watch> watches;
|
||||
|
||||
@Autowired
|
||||
public DefaultEventWatcher(KubernetesClient kubernetesClient) {
|
||||
this.kubernetesClient = kubernetesClient;
|
||||
this.watches = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
@Async
|
||||
public void addWatch(String name, Function<KubernetesClient, Watch> watch) {
|
||||
if (watches.containsKey(name)) {
|
||||
throw new IllegalArgumentException("Watch already present: " + name);
|
||||
}
|
||||
|
||||
watches.put(name, watch.apply(kubernetesClient));
|
||||
log.info("Added new Kubernetes watch: {}", name);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void unwatch() {
|
||||
if (this.watches != null) {
|
||||
for (Watch watch : this.watches.values()) {
|
||||
try {
|
||||
watch.close();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error while closing the watch connection", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package io.fabric8.spring.cloud.kubernetes.reload;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* General configuration for the configuration reload.
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.cloud.kubernetes.reload")
|
||||
public class ConfigReloadProperties {
|
||||
|
||||
/**
|
||||
* Enables the Kubernetes configuration reload on change.
|
||||
*/
|
||||
private boolean enabled = false;
|
||||
|
||||
/**
|
||||
* Enables monitoring on config maps to detect changes.
|
||||
*/
|
||||
private boolean monitoringConfigMaps = true;
|
||||
|
||||
/**
|
||||
* Enables monitoring on secrets to detect changes.
|
||||
*/
|
||||
private boolean monitoringSecrets = false;
|
||||
|
||||
/**
|
||||
* Sets the reload strategy for Kubernetes configuration reload on change.
|
||||
*/
|
||||
private ReloadStrategy strategy = ReloadStrategy.REFRESH;
|
||||
|
||||
/**
|
||||
* Sets the detection mode for Kubernetes configuration reload.
|
||||
*/
|
||||
private ReloadDetectionMode mode = ReloadDetectionMode.EVENT;
|
||||
|
||||
/**
|
||||
* Sets the polling period in milliseconds to use when the detection mode is POLLING.
|
||||
*/
|
||||
private Long period = 15000L;
|
||||
|
||||
public ConfigReloadProperties() {
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public boolean isMonitoringConfigMaps() {
|
||||
return monitoringConfigMaps;
|
||||
}
|
||||
|
||||
public void setMonitoringConfigMaps(boolean monitoringConfigMaps) {
|
||||
this.monitoringConfigMaps = monitoringConfigMaps;
|
||||
}
|
||||
|
||||
public boolean isMonitoringSecrets() {
|
||||
return monitoringSecrets;
|
||||
}
|
||||
|
||||
public void setMonitoringSecrets(boolean monitoringSecrets) {
|
||||
this.monitoringSecrets = monitoringSecrets;
|
||||
}
|
||||
|
||||
public ReloadStrategy getStrategy() {
|
||||
return strategy;
|
||||
}
|
||||
|
||||
public void setStrategy(ReloadStrategy strategy) {
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
public ReloadDetectionMode getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public Long getPeriod() {
|
||||
return period;
|
||||
}
|
||||
|
||||
public void setPeriod(Long period) {
|
||||
this.period = period;
|
||||
}
|
||||
|
||||
public void setMode(ReloadDetectionMode mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public enum ReloadStrategy {
|
||||
/**
|
||||
* Fire a refresh of beans annotated with @ConfigurationProperties or @RefreshScope.
|
||||
*/
|
||||
REFRESH,
|
||||
|
||||
/**
|
||||
* Restarts the Spring ApplicationContext to apply the new configuration.
|
||||
*/
|
||||
RESTART_CONTEXT,
|
||||
|
||||
/**
|
||||
* Shuts down the Spring ApplicationContext to activate a restart of the container.
|
||||
* Make sure that the lifecycle of all non-daemon threads is bound to the ApplicationContext and that
|
||||
* a replication controller or replica set is configured to restart the pod.
|
||||
*/
|
||||
SHUTDOWN
|
||||
}
|
||||
|
||||
public enum ReloadDetectionMode {
|
||||
/**
|
||||
* Enables a polling task that retrieves periodically all external properties and
|
||||
* fire a reload when they change.
|
||||
*/
|
||||
POLLING,
|
||||
|
||||
/**
|
||||
* Listens to Kubernetes events and checks if a reload is needed when configmaps or secrets change.
|
||||
*/
|
||||
EVENT
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package io.fabric8.spring.cloud.kubernetes.reload;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import io.fabric8.kubernetes.client.Client;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.env.CompositePropertySource;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
|
||||
/**
|
||||
* This is the superclass of all beans that can listen to changes in the configuration and fire a reload.
|
||||
*/
|
||||
public abstract class ConfigurationChangeDetector {
|
||||
|
||||
protected Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
protected ConfigurableEnvironment environment;
|
||||
|
||||
protected ConfigReloadProperties properties;
|
||||
|
||||
protected KubernetesClient kubernetesClient;
|
||||
|
||||
protected ConfigurationUpdateStrategy strategy;
|
||||
|
||||
public ConfigurationChangeDetector(ConfigurableEnvironment environment, ConfigReloadProperties properties, KubernetesClient kubernetesClient, ConfigurationUpdateStrategy strategy) {
|
||||
this.environment = environment;
|
||||
this.properties = properties;
|
||||
this.kubernetesClient = kubernetesClient;
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void shutdown() {
|
||||
// Ensure the kubernetes client is cleaned up from spare threads when shutting down
|
||||
if (kubernetesClient instanceof Client) {
|
||||
((Client) kubernetesClient).close();
|
||||
}
|
||||
}
|
||||
|
||||
public void reloadProperties() {
|
||||
log.info("Reloading using strategy: " + strategy.getName());
|
||||
strategy.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if two property sources are different.
|
||||
*/
|
||||
protected boolean changed(MapPropertySource mp1, MapPropertySource mp2) {
|
||||
if (mp1 == mp2) return false;
|
||||
if (mp1 == null && mp2 != null || mp1 != null && mp2 == null) return true;
|
||||
|
||||
Map<String, Object> s1 = mp1.getSource();
|
||||
Map<String, Object> s2 = mp2.getSource();
|
||||
|
||||
return s1 == null ? s2 != null : !s1.equals(s2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds one registered property source of the given type, logging a warning if
|
||||
* multiple property sources of that type are available.
|
||||
*/
|
||||
protected <S extends PropertySource<?>> S findPropertySource(Class<S> sourceClass) {
|
||||
List<S> sources = findPropertySources(sourceClass);
|
||||
if (sources.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
if (sources.size() > 1) {
|
||||
log.warn("Found more than one property source of type " + sourceClass);
|
||||
}
|
||||
return sources.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all registered property sources of the given type.
|
||||
*/
|
||||
protected <S extends PropertySource<?>> List<S> findPropertySources(Class<S> sourceClass) {
|
||||
List<S> managedSources = new LinkedList<>();
|
||||
|
||||
LinkedList<PropertySource<?>> sources = toLinkedList(environment.getPropertySources());
|
||||
while (!sources.isEmpty()) {
|
||||
PropertySource<?> source = sources.pop();
|
||||
if (source instanceof CompositePropertySource) {
|
||||
CompositePropertySource comp = (CompositePropertySource) source;
|
||||
sources.addAll(comp.getPropertySources());
|
||||
} else if (sourceClass.isInstance(source)) {
|
||||
managedSources.add(sourceClass.cast(source));
|
||||
}
|
||||
}
|
||||
|
||||
return managedSources;
|
||||
}
|
||||
|
||||
private <E> LinkedList<E> toLinkedList(Iterable<E> it) {
|
||||
LinkedList<E> list = new LinkedList<E>();
|
||||
for (E e : it) {
|
||||
list.add(e);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package io.fabric8.spring.cloud.kubernetes.reload;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* This is the superclass of all named strategies that can be fired when the configuration changes.
|
||||
*/
|
||||
public class ConfigurationUpdateStrategy {
|
||||
|
||||
private String name;
|
||||
|
||||
private Runnable reloadProcedure;
|
||||
|
||||
public ConfigurationUpdateStrategy(String name, Runnable reloadProcedure) {
|
||||
Objects.requireNonNull(name, "name cannot be null");
|
||||
Objects.requireNonNull(reloadProcedure, "reloadProcedure cannot be null");
|
||||
this.name = name;
|
||||
this.reloadProcedure = reloadProcedure;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
this.reloadProcedure.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("ConfigurationUpdateStrategy{");
|
||||
sb.append("name='").append(name).append('\'');
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package io.fabric8.spring.cloud.kubernetes.reload;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.ConfigMap;
|
||||
import io.fabric8.kubernetes.api.model.Secret;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.KubernetesClientException;
|
||||
import io.fabric8.kubernetes.client.Watcher;
|
||||
import io.fabric8.spring.cloud.kubernetes.config.ConfigMapPropertySource;
|
||||
import io.fabric8.spring.cloud.kubernetes.config.ConfigMapPropertySourceLocator;
|
||||
import io.fabric8.spring.cloud.kubernetes.config.SecretsPropertySource;
|
||||
import io.fabric8.spring.cloud.kubernetes.config.SecretsPropertySourceLocator;
|
||||
|
||||
import org.springframework.core.env.AbstractEnvironment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
|
||||
/**
|
||||
* A change detector that subscribes to changes in secrets and configmaps and fire a reload when something changes.
|
||||
*/
|
||||
public class EventBasedConfigurationChangeDetector extends ConfigurationChangeDetector {
|
||||
|
||||
private ConfigMapPropertySourceLocator configMapPropertySourceLocator;
|
||||
|
||||
private SecretsPropertySourceLocator secretsPropertySourceLocator;
|
||||
|
||||
private EventWatcher eventWatcher;
|
||||
|
||||
public EventBasedConfigurationChangeDetector(AbstractEnvironment environment,
|
||||
ConfigReloadProperties properties,
|
||||
KubernetesClient kubernetesClient,
|
||||
ConfigurationUpdateStrategy strategy,
|
||||
ConfigMapPropertySourceLocator configMapPropertySourceLocator,
|
||||
SecretsPropertySourceLocator secretsPropertySourceLocator,
|
||||
EventWatcher eventWatcher) {
|
||||
super(environment, properties, kubernetesClient, strategy);
|
||||
|
||||
this.eventWatcher = eventWatcher;
|
||||
this.configMapPropertySourceLocator = configMapPropertySourceLocator;
|
||||
this.secretsPropertySourceLocator = secretsPropertySourceLocator;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void watch() {
|
||||
|
||||
if (properties.isMonitoringConfigMaps()) {
|
||||
eventWatcher.addWatch("config-maps-watch", k -> k.configMaps()
|
||||
.watch(new Watcher<ConfigMap>() {
|
||||
@Override
|
||||
public void eventReceived(Action action, ConfigMap configMap) {
|
||||
onEvent(configMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(KubernetesClientException e) {
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
if (properties.isMonitoringSecrets()) {
|
||||
eventWatcher.addWatch("secrets-watch", k -> k.secrets()
|
||||
.watch(new Watcher<Secret>() {
|
||||
@Override
|
||||
public void eventReceived(Action action, Secret secret) {
|
||||
onEvent(secret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(KubernetesClientException e) {
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
log.info("Kubernetes polling configuration change detector activated");
|
||||
}
|
||||
|
||||
private void onEvent(ConfigMap configMap) {
|
||||
MapPropertySource currentConfigMapSource = findPropertySource(ConfigMapPropertySource.class);
|
||||
if (currentConfigMapSource != null) {
|
||||
MapPropertySource newConfigMapSource = configMapPropertySourceLocator.locate(environment);
|
||||
if (changed(currentConfigMapSource, newConfigMapSource)) {
|
||||
log.info("Detected change in config maps");
|
||||
reloadProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onEvent(Secret secret) {
|
||||
MapPropertySource currentSecretSource = findPropertySource(SecretsPropertySource.class);
|
||||
if (currentSecretSource != null) {
|
||||
MapPropertySource newSecretSource = secretsPropertySourceLocator.locate(environment);
|
||||
if (changed(currentSecretSource, newSecretSource)) {
|
||||
log.info("Detected change in secrets");
|
||||
reloadProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.fabric8.spring.cloud.kubernetes.reload;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.Watch;
|
||||
|
||||
/**
|
||||
* Provides a way to start Kubernetes watches and bind their lifecycle to the application context.
|
||||
*/
|
||||
public interface EventWatcher {
|
||||
|
||||
void addWatch(String name, Function<KubernetesClient, Watch> watch);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package io.fabric8.spring.cloud.kubernetes.reload;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.spring.cloud.kubernetes.config.ConfigMapPropertySource;
|
||||
import io.fabric8.spring.cloud.kubernetes.config.ConfigMapPropertySourceLocator;
|
||||
import io.fabric8.spring.cloud.kubernetes.config.SecretsPropertySource;
|
||||
import io.fabric8.spring.cloud.kubernetes.config.SecretsPropertySourceLocator;
|
||||
|
||||
import org.springframework.core.env.AbstractEnvironment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
/**
|
||||
* A change detector that periodically retrieves secrets and configmaps and fire a reload when something changes.
|
||||
*/
|
||||
public class PollingConfigurationChangeDetector extends ConfigurationChangeDetector {
|
||||
|
||||
private ConfigMapPropertySourceLocator configMapPropertySourceLocator;
|
||||
|
||||
private SecretsPropertySourceLocator secretsPropertySourceLocator;
|
||||
|
||||
public PollingConfigurationChangeDetector(AbstractEnvironment environment,
|
||||
ConfigReloadProperties properties,
|
||||
KubernetesClient kubernetesClient,
|
||||
ConfigurationUpdateStrategy strategy,
|
||||
ConfigMapPropertySourceLocator configMapPropertySourceLocator,
|
||||
SecretsPropertySourceLocator secretsPropertySourceLocator) {
|
||||
super(environment, properties, kubernetesClient, strategy);
|
||||
|
||||
this.configMapPropertySourceLocator = configMapPropertySourceLocator;
|
||||
this.secretsPropertySourceLocator = secretsPropertySourceLocator;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
log.info("Kubernetes polling configuration change detector activated");
|
||||
}
|
||||
|
||||
@Scheduled(initialDelayString = "${spring.cloud.kubernetes.reload.period:15000}", fixedDelayString = "${spring.cloud.kubernetes.reload.period:15000}")
|
||||
public void executeCycle() {
|
||||
|
||||
boolean changedConfigMap = false;
|
||||
if (properties.isMonitoringConfigMaps()) {
|
||||
MapPropertySource currentConfigMapSource = findPropertySource(ConfigMapPropertySource.class);
|
||||
if (currentConfigMapSource != null) {
|
||||
MapPropertySource newConfigMapSource = configMapPropertySourceLocator.locate(environment);
|
||||
changedConfigMap = changed(currentConfigMapSource, newConfigMapSource);
|
||||
}
|
||||
}
|
||||
|
||||
boolean changedSecrets = false;
|
||||
if (properties.isMonitoringSecrets()) {
|
||||
MapPropertySource currentSecretSource = findPropertySource(SecretsPropertySource.class);
|
||||
if (currentSecretSource != null) {
|
||||
MapPropertySource newSecretSource = secretsPropertySourceLocator.locate(environment);
|
||||
changedSecrets = changed(currentSecretSource, newSecretSource);
|
||||
}
|
||||
}
|
||||
|
||||
if (changedConfigMap || changedSecrets) {
|
||||
reloadProperties();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
io.fabric8.spring.cloud.kubernetes.KubernetesAutoConfiguration
|
||||
io.fabric8.spring.cloud.kubernetes.KubernetesAutoConfiguration,\
|
||||
io.fabric8.spring.cloud.kubernetes.reload.ConfigReloadAutoConfiguration
|
||||
|
||||
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
|
||||
io.fabric8.spring.cloud.kubernetes.config.BootstrapConfiguration
|
||||
|
||||
Reference in New Issue
Block a user