Spring Cloud Kubernetes - Spring boot fails to start when config reload is enabled

1/2/2019

I have this demo project which prints a label that is read from the configurations.

This is my main class:

@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class DemoApplication {

    private MyConfig config;
    private DiscoveryClient discoveryClient;

    @Autowired
    public DemoApplication(MyConfig config, DiscoveryClient discoveryClient) {
        this.config = config;
        this.discoveryClient = discoveryClient;
    }

    @RequestMapping("/")
    public String info() {
        return config.getMessage();
    }

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

    @RequestMapping("/services")
    public String services() {
        StringBuilder b = new StringBuilder();
        discoveryClient.getServices().forEach((s) -> b.append(s).append(" , "));
        return b.toString();
    }

}

And the MyConfig class is:

@Configuration
@ConfigurationProperties(prefix = "bean")
public class MyConfig {

    private String message = "a message that can be changed live";

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

The bootstrap.properties contain:

spring.application.name=demo
spring.cloud.kubernetes.config.name=demo
spring.cloud.kubernetes.config.enabled=true
spring.cloud.kubernetes.config.namespace=default
spring.cloud.kubernetes.reload.enabled=true
spring.cloud.kubernetes.reload.monitoring-config-maps=true
spring.cloud.kubernetes.reload.strategy=refresh
spring.cloud.kubernetes.reload.mode=event
management.endpoint.refresh.enabled=true
management.endpoints.web.exposure.include=*

And the dependencies in build.gradle:

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-actuator")
    compile("org.springframework.cloud:spring-cloud-starter-kubernetes:+")
    compile("org.springframework.cloud:spring-cloud-starter-kubernetes-config:+")
    testCompile('org.springframework.boot:spring-boot-starter-test')
    runtime("org.springframework.boot:spring-boot-properties-migrator")
}

I'm creating the ConfigMap with kubectl create -f configmap-demo.yml being the content:

apiVersion: v1
kind: ConfigMap
metadata:
  name: demo
data:
    bean.message: This is an info from k8

When deploying in Kubernetes I get the following error on Spring Boot startup:

2019-01-02 13:41:41.462  INFO 1 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'configurationPropertiesRebinderAutoConfiguration' of type [org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$EnhancerBySpringCGLIB$e13002af] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.1.RELEASE)

2019-01-02 13:41:41.940  INFO 1 --- [           main] b.c.PropertySourceBootstrapConfiguration : Located property source: ConfigMapPropertySource {name='configmap.demo.default'}
2019-01-02 13:41:41.942  INFO 1 --- [           main] b.c.PropertySourceBootstrapConfiguration : Located property source: SecretsPropertySource {name='secrets.demo.default'}
2019-01-02 13:41:42.030  INFO 1 --- [           main] com.example.demo.DemoApplication         : The following profiles are active: kubernetes
2019-01-02 13:41:43.391  INFO 1 --- [           main] o.s.cloud.context.scope.GenericScope     : BeanFactory id=416ee750-8ebb-365d-9114-12b51acaa1e0
2019-01-02 13:41:43.490  INFO 1 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration' of type [org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$EnhancerBySpringCGLIB$e13002af] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2019-01-02 13:41:43.917  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-01-02 13:41:43.952  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-01-02 13:41:43.953  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/9.0.13
2019-01-02 13:41:43.969  INFO 1 --- [           main] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/usr/lib/jvm/java-1.8-openjdk/jre/lib/amd64/server:/usr/lib/jvm/java-1.8-openjdk/jre/lib/amd64:/usr/lib/jvm/java-1.8-openjdk/jre/../lib/amd64:/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib]
2019-01-02 13:41:44.156  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-01-02 13:41:44.157  INFO 1 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2033 ms
2019-01-02 13:41:44.957  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-01-02 13:41:45.353  WARN 1 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'propertyChangeWatcher' defined in class path resource [org/springframework/cloud/kubernetes/config/reload/ConfigReloadAutoConfiguration$ConfigReloadAutoConfigurationBeans.class]: Unsatisfied dependency expressed through method 'propertyChangeWatcher' parameter 1; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'configurationUpdateStrategy' defined in class path resource [org/springframework/cloud/kubernetes/config/reload/ConfigReloadAutoConfiguration$ConfigReloadAutoConfigurationBeans.class]: Unsatisfied dependency expressed through method 'configurationUpdateStrategy' parameter 2; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.cloud.context.restart.RestartEndpoint' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
2019-01-02 13:41:45.358  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'
2019-01-02 13:41:45.370  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2019-01-02 13:41:45.398  INFO 1 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-01-02 13:41:45.612 ERROR 1 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 2 of method configurationUpdateStrategy in org.springframework.cloud.kubernetes.config.reload.ConfigReloadAutoConfiguration$ConfigReloadAutoConfigurationBeans required a bean of type 'org.springframework.cloud.context.restart.RestartEndpoint' that could not be found.

The following candidates were found but could not be injected:
        - Bean method 'restartEndpoint' in 'RestartEndpointWithIntegrationConfiguration' not loaded because @ConditionalOnClass did not find required class 'org.springframework.integration.monitor.IntegrationMBeanExporter'
        - Bean method 'restartEndpointWithoutIntegration' in 'RestartEndpointWithoutIntegrationConfiguration' not loaded because @ConditionalOnEnabledEndpoint no property management.endpoint.restart.enabled found so using endpoint default


Action:

Consider revisiting the entries above or defining a bean of type 'org.springframework.cloud.context.restart.RestartEndpoint' in your configuration.

If I set spring.cloud.kubernetes.reload.enabled to false everything works and the configmap is read and put to use. Now my goal is to reload the configuration if the configmap changes but get the exception seen above. I can invoke /actuator/refresh manually so I don't think it is the lack of the availability for the refresh endpoint.

I created a demo project with all included at https://drive.google.com/open?id=1QbP8vePALLZ2hWQJArnyxrzSySuXHKiz .

-- Miguel Ribeiro
kubernetes
spring
spring-boot
spring-boot-actuator
spring-cloud-kubernetes

1 Answer

1/2/2019

It starts if you set management.endpoint.restart.enabled=true

The message tells you that it can't load a RestartEndpoint bean. None was created because there's two ways it could be loaded and nether was satisfied:

  • Bean method 'restartEndpoint' in 'RestartEndpointWithIntegrationConfiguration' not loaded because @ConditionalOnClass did not find required class 'org.springframework.integration.monitor.IntegrationMBeanExporter'

Well you're not using spring integration so I guess you don't want this path - you want the other one.

  • Bean method 'restartEndpointWithoutIntegration' in 'RestartEndpointWithoutIntegrationConfiguration' not loaded because @ConditionalOnEnabledEndpoint no property management.endpoint.restart.enabled found so using endpoint default

So we need to set management.endpoint.restart.enabled=true, which is also set in the official reload example project. Without setting this the RestartEndpoint bean that we require will not be loaded.

-- Ryan Dawson
Source: StackOverflow