We are considering using the config server for our multi-tenant SaaS application, but have trouble finding an elegant way to do it with the actual state of the config server. Or maybe we are missing something, if so please tell us :)
Our application serves multiple customers, or tenants, and each of them have specific configuration.
Examples of tenant aware configuration are - Structure of analytical levels - Activation of optional modules (e.g. project, sales...) - Configuration of widgets - Injections to run and their configuration - Emails of people to notify of various events
With spring cloud config server, there are 2 options identified - A branch per tenant, specify it in the label part. Can use a list of labels to "inherit" standard properties, e.g. label=tenantXXX,standard. Another option is to git merge manually. - A properties file with a different name, e.g. accounting-service-tenantXXX.properties. Inherit mechanism can easily be set up by using 2 variables : one for standard property (static), and another optional one for tenant specific (dynamic based on tenant).
In any case the client side cannot be used as-is due to its inability to dynamically set the label (option 1) or the application name (option 2).
Instead, a simple config client based on RestTemplate could be developed to get tenant aware configuration through the config server, but this means bypassing the client part of config server entirely.
Some kind of support to achieve in a more elegant way would be nice.
Any comment appreciated
Comment From: dsyer
I'd like to understand the use case a bit more. What does "Configuration of widgets" mean?
Note that the config client is designed to initialize a single ApplicationContext
(or more specifically its Environment
). How is that relevant to your multi-tenant scenario? Wouldn't you be able to create a new ApplicationContext
per tenant? It seems like you would need some way to separate their concerns anyway, so why not in that way?
Comment From: lejeunen
The widget thing means data analysis components (mostly charts) of the application are sometimes deactivated for some tenant, and activated for others. Similar to a configurable dashboard.
I hadn't thought about having an ApplicationContext per tenant. An instance of our service might serve hundreds of different tenants, do you think it would be adequate to have hundreds of contexts?
More generally, do you think using the config server to manage business configuration like ours is adequate?
Thank you for your feedback.
Comment From: dsyer
do you think it would be adequate to have hundreds of contexts
Your use of the word "adequate" is probably just a translation from something in French that wouldn't help me. Certainly an ApplicationContext
can be very lightweight, so it's not a problem to have them lying around, or even to create them quickly and throw them away (as long as you are very careful with what's in them - you don't want to reboot Hibernate every time you call a method for instance). Spring Cloud (Context and Config) already uses a lot of throwaway ApplicationContexts
mainly just to load property sources. If you design how they are created and used carefully, I don't suppose there would be any technical issues at the Spring level.
do you think using the config server to manage business configuration like ours is adequate
It depends a lot what you mean by "adequate". If I were you I'd do a spike and see what you can learn.
Comment From: lejeunen
By adequate I mean appropriate; would the resulting design be elegant and robust.
Thanks for your comments
Comment From: dsyer
I don't see why not. But the devil as usual will be in the detail.
Comment From: gonzalad
Hello,
I also need a Configuration Server with multi-tenant support.
I'm adding our scenario (in case it can bring some ideas/requirements/needs related to multi-tenancy) :
Scenario
- a tenant is created whenever we register a new customer in our platform.
- the creation step will create tenant-specific resources (i.e. db-schemas, credentials, ...).
At boot-time, our Spring Application will only need classic Configuration data (application/profiles/label as usual).
But at runtime (i.e. when serving a request), our application will need sometimes tenant-specific data (i.e. when accessing a tenant-specific db schema).
Our tenant information is available from the Thread-Context (TenantContextHolder -> TenantContext -> tenantId).
Having a multi-tenant friendly Spring Cloud Config Server would help here.
But Perhaps it would require quite a lot of change in Spring Config Server. i.e. Config Server supports lookup based on application/profile/label, and we would need to add lookup based on tenant (we can even imagine adding multiple lookups to allow anyone to add custom search criterias for config).
Also, we'll need to handle lookup key search order.
Otherwise we can implement a hacky-solution (i.e. prefixing or suffixing config key with tenant specific information).
Sample
Tenant Creation Step
Let's say we register a new customer, which create a new tenant. Tenant creation triggers a tenant-specific mongo db creation.
We'll register into our Configuration Server: * tenant-specific MongoDb URL (i.e. key app1.mongo.url). * tenant-specific MongoDb Databasename (i.e. key app1.mongo.databaseName).
Accessing tenant-info from our Spring app
Somewhere in our code, we'll have something like :
public class SomeHelperClass {
@Autowired
private Environment ;
public DB getTenantDb() {
final MongoClientURI databaseURI = provider.getDatabaseURI();
MongoClient mongoClient = new MongoClient(environment.getRequiredProperty("app1.mongo.uri"));
return MongoDbUtils.getDB(mongoClient, environment.getRequiredProperty("app1.mongo.databaseName"));
}
}
We could even imagine having :
public class SomeHelperClass {
@Autowired
private Environment ;
@ConfigurationProperties(prefix = "app1.mongo")
@TenantScope
@Bean
public TenantDbProperties tenantDbProperties() {
...
}
}
Let me know if it can be worthwhile to add this kind of support in Spring Config Server, or if I'm out of the road.
Cheers, Adrian
Comment From: spencergibb
So far, only two people have asked for this. Config Server isn't meant to be accessed at request time. Whatever is done would have to work with normal spring boot properties (since that is all config server uses). My guess is that it would be a large change.
Comment From: ghoddg
Same requirement is for me also to have config server which should the multi-tenancy support. The requirement is like each tenant can have its own configuration details which should be available when request came for tenant. e.g. Tenant A: key=message Value = I am Tenant A Tenent B: key=message Value = I am Tenant B
here when request come for Tenant A for key=message, then it should provide Value = I am Tenant A. The same client (one Application Context) should server this and there is no client deployment per Tenant
If have the project of such feature, please provide github link.
Comment From: fjmpaez911
I also need to have the multi-tenancy support but I think that it's already there. This is my way to achieve it:
spring:
cloud:
config:
server:
git:
uri: https://github.com/company/default-config-repo
username: abcd
password: 1234
clone-on-start: true
search-paths: '*'
repos:
tenant1:
pattern: '*/tenant1*'
uri: https://github.com/company/tenant1-config-repo
username: zzzz
password: 1234
clone-on-start: true
search-paths: '*'
tenant2:
pattern: '*/tenant2*'
uri: https://github.com/company/tenant2-config-repo
username: yyyy
password: 1234
clone-on-start: true
search-paths: '*'
The idea is explained in the documentation. The approach here is to have different repositories per tenant and use different profiles in order to know which tenant applies in each case
Comment From: snowe2010
Late to the party, but using different repos for tenant config doesn't work because then you must modify your bootstrap whenever you add a new tenant. This is not acceptable for many reasons, the point of config server is that you can refresh configuration just by adding configuration to your source. It would be ideal to have a tenant solution, as splitting your configuration in this way is not really desirable.
Some other reasons that the bootstrap method doesn't work is that you can't then share default configurations between these different tenants, you must duplicate that config across many repos. You also duplicate environment profiles and labels across the repos.
Comment From: spencergibb
can't you use placeholders?
Comment From: snowe2010
@spencergibb what do you mean by placeholders?
Comment From: snowe2010
something like tenant-*
?
Comment From: spencergibb
https://cloud.spring.io/spring-cloud-static/spring-cloud-config/2.2.2.RELEASE/reference/html/#_placeholders_in_git_uri
Comment From: snowe2010
@spencergibb I had updated my comment right before you commented, so you probably didn't see, but that doesn't solve the problem of using defaults. Those won't merge with your tenant specific stuff so now you have to maintain significantly more configuration that keeps growing based on the number of tenants you have.
Comment From: spencergibb
how about composite configuration?
Comment From: snowe2010
Hm. Looking at that it seems it might interfere with our Vault config?
Currently have this:
spring:
profiles:
include:
- git
- vault
application:
name: configuration-management
cloud:
config:
server:
vault:
...
git:
uri: git@github.com:company/config.git
search-paths: "private/,private/{application},global/,public/"
...
repos:
public:
pattern: factory*
search-paths: "global/,public/,public/{application}"
uri: git@github.com:company/config.git
...
private:
pattern: differentpattern*
search-paths: "private/,private/{application},global/"
uri: git@github.com:company/config.git
...
bootstrap: false
fail-fast: true
note that the private
pattern just searches different search paths, since we were trying to keep our config all in a single application.
Wondering how I would modify this to accomplish a composite repository with a vault configuration and this split search path stuff.
Comment From: snowe2010
@spencergibb even with a composite configuration, spring doesn't provide placeholders for things like tenant. We'd still have one extra variable that spring can't handle. So say we did a composite config like so
composite:
- type: git
uri: git@github.com:promontech/base-config.git
- type: git
uri: git@github.com:promontech/config-tenant-{whatgoeshere?}.git
what placeholder do I use in the uri?
Comment From: spencergibb
how do you define a tenant?
Comment From: snowe2010
What do you mean by define? Our tenants are companies that buy our software. We are working towards making it so that they can add themselves to our platform, add their own configuration, etc. Essentially we don't want to have to push code changes to add a new tenant. So we have several applications, that will have different configurations based on the environment and the tenant. If one tenant wants a certain feature turned on but another tenant does not, then we need SCCS to support that. We would like to have this all present in a single repository, so that our frontend and backend can share certain global toggles, like whether to turn certain features on, but if that's not possible at the moment I understand.
If you are asking how we add a new tenant, currently we have a bunch of json files that are environment, tenant, and application specific that we're moving to be in SCCS. We have to manually update about 90 files every time we make a single configuration change, so you can see why this is kind of important to me, as that number is based on the number of clients/tenants we have.
Comment From: spencergibb
there's some property on an app that defines a tenant? can't that go in as a spring.cloud.config.profile
?
Comment From: snowe2010
That's currently the solution we're looking at, but we're still overloading the concept because we then need to make the profile environment specific. so something like application-tenant_a_dev.yml
and application-tenant_b_dev.yml
.
~~We tried nesting the tenants in a app/profile/app-envprofile.yml
kind of situation, but SCCS doesn't seem to like that very much~~
~~search-paths: "global/,public/,public/{application},public/{application}/{profile}/"
<- didn't work.~~ I actually managed to get this working.
I've been trying to figure it out using the multiple profile solution for almost the whole day now. We need to pass two separate profiles, the environment and the tenant. So something like dev
and tenant_a
. I tried a structure like /application/tenant_a/application-dev.yml
as well as using single file yaml docs like /application/application-tenant_a.yml
where the yaml had separated profiles like
defaults:
TENANT_NAME: Tenant A
---
##### DEV ######
spring.profiles: dev & tenant_a
additional: config
---
##### PROD ######
spring.profiles: prod & tenant_a
additional: config
but only the default document at the top of the file is picked up.
edit:
I managed to get the nested {application}/{profile}/application-{envprofile}.yml
style working, so I guess that's what we'll have to go with until (if) spring decides to support this.
Comment From: jakub-moravec
Hi, @snowe2010!
I'm trying to achieve the same thing you did, but I'm not sure I understand your solution fully. What is the difference between {profile}
and {envprofile}
in your latest comment? {profile}
is the Spring profile ... but how do you pass the {envprofile}
value via the Spring Cloud Config Client? Thanks!
Comment From: igorbljahhin
Hello! I am working on same task. Did you find the solution to use Spring Cloud Config in multi-tenant application?
Comment From: snowe2010
@jakub-moravec I'm sorry, I completely forgot about this. I do not remember what I had done there. I'm sorry :( I think I was taking advantage of some mechanism of spring that allowed for 3 different tiers. I will send this to a former colleague that still works at that company and he can chime in if he finds the time.