EUREKA-846 Spike - Multi-Version Application Deployment in a Single Eureka Cluster
- 1 Spike Overview
- 1.1 1. Purpose and Scope
- 1.2 2. Business Requirement
- 1.3 3. Data Model and Entitlement Isolation
- 1.4 3.1 Application Entitlements
- 1.5 3.2 Module Entitlements
- 1.6 4. Discovery, Sidecars, Kafka, and Secrets
- 1.7 4.1 Service Discovery and Module IDs
- 1.8 4.2 What Is Sent to Sidecars
- 1.9 5. Kafka and Secure Store Behaviour in This Pattern
- 1.10 5.1 Kafka Topics (Environment-Level, Not Tenant-Level)
- 1.11 5.2 Secure Store (Shared Backend, Namespaced Keys)
- 1.12 6. The ENV Mismatch Problem Observed
- 1.13 6.1 Configuration Used
- 1.14 6.2 Symptoms
- 1.15 6.3 Likely Root Cause
- 1.16 7. Target Architecture: Multi-Version in One Cluster
- 1.17 7.1 Design Principles
- 1.18 7.2 Proposed Pattern
- 1.19 8. Recommended Configuration
- 1.20 8.1 Environment Variables
- 1.21 8.2 Versioned Module IDs and Discovery
- 1.22 9 Sidecar Logic for Multi-Version Support
- 1.23 9.1 Version-Specific Entitlement Loading
- 1.24 9.2 Runtime Event Filtering
- 1.25 9.3 Traffic Enforcement
- 1.26 10. Summary
- 1.27 Kafka topic routing with multiple module versions
- 1.28 How this is handled with Okapi
- 1.29 Applying a similar pattern with Eureka
- 1.30 Base system state
- 1.31 Highlighted rule: ENV usage for sidecars vs modules
- 1.32 Migration and realignment
- 1.33 Compatibility requirements
- 1.34 Application releases and multi-version modules
- 1.35 Single environment: no ENV per module version
- 1.36 How topic isolation used to work
- 1.37 Required behavior: modules must filter by entitlement
- 1.38 Summary of the only allowed pattern
Spike Overview
EUREKA-846: Investigate tenant not enabled errors during entitlement (mod-lists/mod-fqm)Closed
1. Purpose and Scope
This document describes how the Eureka-based FOLIO platform supports running multiple versions of the same application in a single Kubernetes cluster, where tenants can:
Share some modules at the same version (e.g.
mod-a-2.0.0), andUse different versions of other modules (e.g.
mod-b-1.0.0vsmod-b-1.0.1) per tenant.
2. Business Requirement
As a multi-tenant hoster, we must:
Migrate only selected tenants to a new critical patch (CSP-2) while others stay on a stable release (CSP-1).
Run multiple releases of the same application in a single cluster.
Allow tenants on different releases to share modules where versions are identical, and diverge where versions differ.
Example requirement:
Tenant
foo(CSP-1) runsapp-xyz-1.0.0Tenant
bar(CSP-2) runsapp-xyz-1.0.1Both tenants share
mod-a-2.0.0foousesmod-b-1.0.0,barusesmod-b-1.0.1
This must be achieved without cross-tenant leakage
3. Data Model and Entitlement Isolation
The current architecture already supports this pattern at the data and entitlement layer.
3.1 Application Entitlements
Table: entitlement
Primary key: (tenant_id, application_id)
This allows each tenant to be entitled to a specific application version:
tenant_id | application_id |
|---|---|
foo | app-xyz-1.0.0 |
bar | app-xyz-1.0.1 |
Effect:
mgr-tenant-entitlementstreats entitlement forfoo/app-xyz-1.0.0andbar/app-xyz-1.0.1as independent.
3.2 Module Entitlements
Table: entitlement_module
Composite key: (module_id, tenant_id, application_id)
This supports both shared and version-divergent modules.
Shared module (mod-a-2.0.0):
module_id | tenant_id | application_id | note |
|---|---|---|---|
mod-a-2.0.0 | foo | app-xyz-1.0.0 | Installed for Tenant foo |
mod-a-2.0.0 | bar | app-xyz-1.0.1 | Installed for Tenant bar |
Logically “shared”, but represented as two entitlement records, so actions on one tenant do not affect the other.
Version-specific module (mod-b):
module_id | tenant_id | application_id | note |
|---|---|---|---|
mod-b-1.0.0 | foo | app-xyz-1.0.0 | Used only by Tenant foo |
mod-b-1.0.1 | bar | app-xyz-1.0.1 | Used only by Tenant bar |
Effects:
Tenant isolation: Every entitlement and module-entitlement record includes
tenant_id, so tenants are isolated.Version isolation: Different module IDs (
mod-b-1.0.0vsmod-b-1.0.1) prevent collisions.Shared modules: “Sharing” means multiple tenants have entitlements for the same
module_idand version.
Conclusion: From a persistence and entitlement perspective, this scenario is supported .
4. Discovery, Sidecars, Kafka, and Secrets
4.1 Service Discovery and Module IDs
Eureka rely on module IDs including the version (e.g. mod-b-1.0.0) as a convention for uniquely identifying module instances.
Different module IDs → distinct discovery entries and URLs.
Example discovery registry entries:
mod-b-1.0.0→http://mod-b-ramsons:8081mod-b-1.0.1→http://mod-b-sunflower:8081
When mgr-tenant-entitlements resolves “where is mod-b-1.0.1 for tenant bar?”, it gets the explicit Sunflower URL; no guessing based on environment naming is required.
4.2 What Is Sent to Sidecars
Sidecars are configured via application.properties and environment variables, and they receive:
Kafka configuration (topics, bootstrap)
Secure store configuration (backend type, connection)
Environment name (
ENV) and optional patterns for tenant collections / topic regexes
Discovery information per sidecar is not pushed inline in the entitlement payload; instead, modules and sidecars rely on:
Kafka events that carry tenant and module identifiers.
Ingress / Kong / Egress for HTTP routing and service discovery.
5. Kafka and Secure Store Behaviour in This Pattern
5.1 Kafka Topics (Environment-Level, Not Tenant-Level)
Sidecars and manager services use environment-level topics, not per-tenant topics.
Relevant parameters (example):
mp.messaging.incoming.discovery.topic→${ENV:folio}.discoverymp.messaging.incoming.entitlement.topic→${ENV:folio}.entitlementmp.messaging.incoming.logout.topic→ regex-based
Behaviour:
Events for both
fooandbarare published to the same environment topic (e.g.folio.entitlement).Each sidecar filters messages at the module and tenant level:
mod-a-2.0.0sidecar:Processes entitlement events for
(tenant=foo, module=mod-a-2.0.0)and(tenant=bar, module=mod-a-2.0.0).
mod-b-1.0.0sidecar:Listens to the same topic but only processes events for
module_id=mod-b-1.0.0.
mod-b-1.0.1sidecar:Processes only
module_id=mod-b-1.0.1.
This pattern allows shared topics, with logical isolation via filtering.
5.2 Secure Store (Shared Backend, Namespaced Keys)
Sidecars are configured to a single secure store backend (AWS SSM, Vault, filesystem, etc.) and use naming conventions for isolation.
Key parameters:
secret-store.typesecret-store.aws-ssm.*/secret-store.vault.*/secret-store.fssp.*
Key naming convention:
${ENV}_${TENANT}_${KEY}, e.g.:folio_foo_mod-a-secretfolio_bar_mod-a-secretfolio_foo_mod-b-secretfolio_bar_mod-b-secret
Implications:
Isolation is achieved via keys and prefixes, not by switching secret-store configuration per tenant.
All required secrets for both CSP-1 and CSP-2 must be accessible in the configured store.
6. The ENV Mismatch Problem Observed
6.1 Configuration Used
mgr-applications:
ENV = folio
SECURE_STORE_ENV = folio
Release-agnostic and correct: we want mgr-applications to be independent of specific CSP environments.
Modules (e.g. mod-lists, mod-fqm):
ENV = sunflower
SECURE_STORE_ENV = folio
KAFKA_SYS_USER_TOPIC_PATTERN: '(folio\.)(.*\.)mgr-tenant-entitlements.system-user'
KAFKA_CAPABILITIES_TOPIC_PATTERN: '(folio\.)(.*\.)mgr-tenant-entitlements.capability'
KAFKA_JOB_CONSUMER_PATTERN: '(folio\.)(.*\.)mgr-tenant-entitlements\.scheduled-job'
6.2 Symptoms
Entitlement of
platform-minimal,platform-complete, andapp-acquisitionssucceeded.Entitlement of
app-edge-complete,app-dcb, andapp-fqmfailed with:Flow
CANCELLEDdue to:HttpTimeoutExceptioncallinghttp://mod-lists-3-1-5.eureka-sunflower:8082/_/tenant
mod-listslogged:TenantNotEnabledException: Application is not enabled for tenant: dikuwhen callinghttp://fqm/version.
6.3 Likely Root Cause
With ENV=sunflower for modules and ENV=folio for mgr-applications:
Discovery / routing for some modules happened under one logical environment, while entitlements and Kafka events were scoped under another.
Sidecars and modules attempted to call services (
mod-fqm) that were not enabled (or not entitled) in the effective environment/tenant combination.When
ENVwas set tofoliofor modules too, everything aligned (mgr-apps, Kafka topics, and module discovery) and entitlements succeeded.
Conclusion:
The failure is not in the entitlement data model, but in ENV-based environment separation conflicting with discovery and routing expectations.
7. Target Architecture: Multi-Version in One Cluster
The architecture we want:
Single cluster.
One logical Eureka environment (
ENV=folio) for this cluster.Multiple application versions and module versions coexisting:
app-xyz-1.0.0+mod-b-1.0.0(CSP-1)app-xyz-1.0.1+mod-b-1.0.1(CSP-2)
Shared modules (e.g.
mod-a-2.0.0) reused across tenants.
7.1 Design Principles
Tenant isolation via data model (entitlement and entitlement_module).
Version isolation via module ID (module name includes version, as per Okapi convention).
Single environment namespace per cluster for discovery and Kafka (
ENV=folio).Sidecars filter by tenant and module ID rather than relying on ENV differences.
7.2 Proposed Pattern
Keep
ENV=foliofor:mgr-applicationsmgr-tenant-entitlementsAll module deployments and their sidecars
Represent version differences only in module IDs and services:
mod-b-1.0.0vsmod-b-1.0.1
Let
mgr-tenant-entitlementsandmgr-applications:Entitle each tenant to the correct application and module versions.
Resolve discovery URLs per
module_id, not per ENV.
Kafka:
Use shared environment topics (
folio.entitlement,folio.discovery).Sidecars filter by tenant + module ID (already supported).
Secure Store:
Keep
SECURE_STORE_ENV=folioeverywhere.Maintain secrets per tenant and module version via naming convention.
This pattern matches the in-place migration example:
Component | Tenant Foo (Ramsons) | Tenant Bar (Sunflower) | Outcome |
|---|---|---|---|
Application | app-xyz-1.0.0 | app-xyz-1.0.1 | Distinct entitlements |
Shared Mod | mod-a-2.0.0 | mod-a-2.0.0 | Both resolve to the same deployment URL |
Split Mod | mod-b-1.0.0 | mod-b-1.0.1 | Route to different physical module instances |
By ensuring that Sunflower carries a new version (even patch, e.g. 1.0.1), the platform’s version-based routing and discovery can distinguish the two paths cleanly.
8. Recommended Configuration
8.1 Environment Variables
mgr-applications:
ENV = folio
SECURE_STORE_ENV = folio
mgr-tenant-entitlements:
ENV = folio
SECURE_STORE_ENV = folio
All modules (including different versions):
ENV = folio
SECURE_STORE_ENV = folio
KAFKA_SYS_USER_TOPIC_PATTERN: '(folio\.)(.*\.)mgr-tenant-entitlements.system-user'
KAFKA_CAPABILITIES_TOPIC_PATTERN: '(folio\.)(.*\.)mgr-tenant-entitlements.capability'
KAFKA_JOB_CONSUMER_PATTERN: '(folio\.)(.*\.)mgr-tenant-entitlements\.scheduled-job'
8.2 Versioned Module IDs and Discovery
Ensure module IDs follow the
name-versionconvention, e.g.mod-b-1.0.0andmod-b-1.0.1.Register each module version separately in discovery:
mod-b-1.0.0→http://mod-b-1-0-0:8081mod-b-1.0.1→http://mod-b-1-0-1:8081
Make sure
mgr-tenant-entitlementsandmgr-applicationsalways refer to the full module ID.
9 Sidecar Logic for Multi-Version Support
The existing sidecar implementation already provides the necessary guards:
9.1 Version-Specific Entitlement Loading
On startup, a sidecar loads entitlements only for its own
module.id:TenantService#loadTenantsAndEntitlements()calls the tenant entitlement manager with the sidecar’s module ID.Only tenants entitled to that exact module ID are enabled.
module.idis defined as${module.name}-${module.version}inapplication.properties.
Result:
mod-b-1.0.0sidecar enables only tenants entitled tomod-b-1.0.0(e.g.foo).mod-b-1.0.1sidecar enables only tenants entitled tomod-b-1.0.1(e.g.bar).
9.2 Runtime Event Filtering
The entitlement consumer (e.g.
TenantEntitlementConsumer#consume()) processes Kafka events and checks:tenantService.isAssignedModule(moduleId)
If the event’s
moduleIddoes not equal the sidecar’s ownmodule.id, the event is ignored; tenant state is not updated.
Result:
Even though all entitlement changes go to the same
folio.entitlementtopic, each sidecar sees only its own module-version events.
9.3 Traffic Enforcement
The
TenantFilterintercepts all incoming HTTP requests:Reads the
x-okapi-tenantheader.Checks whether that tenant is in the locally enabled tenant set for this sidecar.
If a request for tenant
barhits themod-b-1.0.0sidecar:Tenant
baris not enabled in that sidecar.The filter rejects the request with
400 Bad RequestandUNKNOWN_TENANT.
Result:
Even if routing misconfigurations send tenant
bartraffic to the wrong module version, the sidecar blocks the request at the edge.
10. Summary
The current Eureka/FOLIO architecture already supports multi-version applications per tenant in one cluster through its entitlement schema and versioned module IDs.
The failures observed are due to ENV-based separation causing discovery and routing mismatches, not due to limitations in entitlements.
The recommended solution is to:
Use a single ENV per cluster (
folio),Distinguish module versions by module ID, and Let entitlements and sidecar filtering drive which tenant uses which module version.
The Flag
KONG_TENANT_CHECKS_ENABLEDshould be true for mgr-application to support multiple versions of the same module.EUREKA-789: POC - Version-specific routes in KongClosed
Kafka topic routing with multiple module versions
From recent discussions and comments, we identified that the main concern for hosters is how to route messages correctly to the appropriate Kafka topic when multiple versions of the same module are running in parallel. In particular, the same logical topic may be processed by different module versions, which can lead to incompatibilities if not properly isolated.
How this is handled with Okapi
When you need to run different versions of a module that use Kafka topics, you should follow the approach described in the RFC “Kafka tenant collection topics”, section Migration to New Module Version:
https://github.com/folio-org/rfcs/blob/master/text/0002-kafka-tenant-collection-topics.md
Key rule:
Incompatible module versions must be separated at the Kafka topic level.
Common pattern:
Use the
ENVenvironment variable to populate the[environment]part of the Kafka topic name, for example:ENV=nolanafor one FOLIO release.ENV=orchidfor another release.
Migration example:
A new version of a module is introduced and rolled out gradually.
The new producer and consumer use a new environment value (for example,
sunflower).