Search code examples
javaspring-bootspring-cloud-configspring-cloud-bus

Spring Cloud Binder: mitigating cost of double bootstrap


We use Spring Cloud Config (Dalston.SR5), with Cloud clients using Spring Boot 2.x, Spring Cloud Bus, and Finchley.SR1.

I understand from this answer why a Cloud Client application bootstraps with Config for the parent SpringBootApplication and then again once the Cloud Bus is bound. I'm fine with that.

My question is whether there is any way to distinguish the two bootstrap requests?

The reason I ask is that our Config server generates credentials and returns them to the client to authenticate with. Two bootstraps means two sets of credentials, only one of which gets used, and this is wasteful.

As far as I can tell the same bootstrap payload is sent each time by ConfigServicePropertySourceLocator, which gives Config no chance.

Is there an override / hook so that I can let Config know not to generate credentials second time around?

(I could tackle from the Config/server side, but that would be a bit desperate, and I'm reluctant to try to manage state - across two otherwise identical requests that just happen to be ~ 20 seconds apart.)


Best idea I have at the moment is to subclass PropertySourceBootstrapConfiguration and update spring.factories as per:

# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.MyCountingPropertySourceBootstrapConfiguration,\

Before making any requests, I should be able to examine the PropertySources and look for any of the properties that the first successful bootstrap would have returned. If present, I'd try to get an additional label or profile into ConfigServicePropertySourceLocator for my Config server to pick up second time around.

I guess that could work, but is there a cleaner / more Spring Boot-y way?


Solution

  • This solution seems to be both simple and very effective.

    New autoconfiguration to take control over ConfigServicePropertySourceLocator:

    @Configuration
    @AutoConfigureBefore(ConfigServiceBootstrapConfiguration.class)
    public class ConfigPropertyLocatorConfiguration {
    
        @Bean
        @ConditionalOnProperty(value = "spring.cloud.config.enabled", matchIfMissing = true)
        public ConfigServicePropertySourceLocator configServicePropertySource(ConfigClientProperties properties) {
            return new CachingConfigServicePropertySourceLocator(properties);
        }
    }
    

    spring.factories:

    org.springframework.cloud.bootstrap.BootstrapConfiguration=\
      autoconfigure.ConfigPropertyLocatorConfiguration
    

    Caching locator implementation:

    public class CachingConfigServicePropertySourceLocator extends
                                               ConfigServicePropertySourceLocator {
    
        private final static Logger LOG = getLogger("...");
    
        private PropertySource<?> cachedProperties;
    
        public CachingConfigServicePropertySourceLocator(ConfigClientProperties props) {
            super(props);
        }
    
        public PropertySource<?> locate(final Environment env) {
            if (cachedProperties == null) {
                cachedProperties = super.locate(env);
            }
            else {
                LOG.debug("Returning cached PropertySource for second bootstrap");
            }
    
            return cachedProperties;
        }
    }
    

    Having been presented with a second opportunity to bootstrap, it seems a little rude to ignore it completely and return the same PropertySource once again - but in our situation this is fine. It prevents us hitting the Config Server twice and wastefully generating credentials.

    No changes to PropertySourceBootstrapConfiguration required. No changes to the Cloud Config Server.