Skip to content

Commit 2b63f78

Browse files
Implemented possibility of traffic splitting configuration (#397)
* Implemented possibility for configuring traffic splitting, and fallback using aggregate cluster #292 --------- Co-authored-by: nastassia.dailidava <[email protected]> Co-authored-by: Kamil Smigielski <[email protected]>
1 parent 689078b commit 2b63f78

File tree

20 files changed

+1092
-115
lines changed

20 files changed

+1092
-115
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22

33
Lists all changes with user impact.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
5-
## [0.20.00]
5+
6+
## [0.20.1]
7+
8+
### Changed
9+
- Implemented configuring traffic splitting and fallback using aggregate cluster functionality
10+
11+
## [0.20.0]
612

713
### Changed
814
- Spring Boot upgraded to 3.1.2

envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt

Lines changed: 83 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import pl.allegro.tech.servicemesh.envoycontrol.groups.Group
1515
import pl.allegro.tech.servicemesh.envoycontrol.groups.IncomingRateLimitEndpoint
1616
import pl.allegro.tech.servicemesh.envoycontrol.groups.ServicesGroup
1717
import pl.allegro.tech.servicemesh.envoycontrol.groups.orDefault
18+
import pl.allegro.tech.servicemesh.envoycontrol.logger
1819
import pl.allegro.tech.servicemesh.envoycontrol.services.MultiClusterState
1920
import pl.allegro.tech.servicemesh.envoycontrol.services.ServiceInstance
2021
import pl.allegro.tech.servicemesh.envoycontrol.services.ServiceInstances
@@ -38,6 +39,7 @@ class EnvoySnapshotFactory(
3839

3940
companion object {
4041
const val DEFAULT_HTTP_PORT = 80
42+
private val logger by logger()
4143
}
4244

4345
fun newSnapshot(
@@ -111,6 +113,7 @@ class EnvoySnapshotFactory(
111113
val removedClusters = previous - current.keys
112114
current + removedClusters
113115
}
116+
114117
false -> current
115118
}
116119
}
@@ -156,24 +159,26 @@ class EnvoySnapshotFactory(
156159
return newSnapshotForGroup
157160
}
158161

159-
private fun getDomainRouteSpecifications(group: Group): Map<DomainRoutesGrouper, Collection<RouteSpecification>> {
162+
private fun getDomainRouteSpecifications(
163+
group: Group
164+
): Map<DomainRoutesGrouper, Collection<RouteSpecification>> {
160165
return group.proxySettings.outgoing.getDomainDependencies().groupBy(
161166
{ DomainRoutesGrouper(it.getPort(), it.useSsl()) },
162167
{
163-
RouteSpecification(
168+
StandardRouteSpecification(
164169
clusterName = it.getClusterName(),
165170
routeDomains = listOf(it.getRouteDomain()),
166-
settings = it.settings
171+
settings = it.settings,
167172
)
168173
}
169174
)
170175
}
171176

172177
private fun getDomainPatternRouteSpecifications(group: Group): RouteSpecification {
173-
return RouteSpecification(
178+
return StandardRouteSpecification(
174179
clusterName = properties.dynamicForwardProxy.clusterName,
175180
routeDomains = group.proxySettings.outgoing.getDomainPatternDependencies().map { it.domainPattern },
176-
settings = group.proxySettings.outgoing.defaultServiceSettings
181+
settings = group.proxySettings.outgoing.defaultServiceSettings,
177182
)
178183
}
179184

@@ -182,30 +187,67 @@ class EnvoySnapshotFactory(
182187
globalSnapshot: GlobalSnapshot
183188
): Collection<RouteSpecification> {
184189
val definedServicesRoutes = group.proxySettings.outgoing.getServiceDependencies().map {
185-
RouteSpecification(
190+
buildRouteSpecification(
186191
clusterName = it.service,
187192
routeDomains = listOf(it.service) + getServiceWithCustomDomain(it.service),
188-
settings = it.settings
193+
settings = it.settings,
194+
group.serviceName,
195+
globalSnapshot
189196
)
190197
}
191198
return when (group) {
192199
is ServicesGroup -> {
193200
definedServicesRoutes
194201
}
202+
195203
is AllServicesGroup -> {
196204
val servicesNames = group.proxySettings.outgoing.getServiceDependencies().map { it.service }.toSet()
197205
val allServicesRoutes = globalSnapshot.allServicesNames.subtract(servicesNames).map {
198-
RouteSpecification(
206+
buildRouteSpecification(
199207
clusterName = it,
200208
routeDomains = listOf(it) + getServiceWithCustomDomain(it),
201-
settings = group.proxySettings.outgoing.defaultServiceSettings
209+
settings = group.proxySettings.outgoing.defaultServiceSettings,
210+
group.serviceName,
211+
globalSnapshot
202212
)
203213
}
204214
allServicesRoutes + definedServicesRoutes
205215
}
206216
}
207217
}
208218

219+
private fun buildRouteSpecification(
220+
clusterName: String,
221+
routeDomains: List<String>,
222+
settings: DependencySettings,
223+
serviceName: String,
224+
globalSnapshot: GlobalSnapshot,
225+
): RouteSpecification {
226+
val trafficSplitting = properties.loadBalancing.trafficSplitting
227+
val weights = trafficSplitting.serviceByWeightsProperties[serviceName]
228+
val enabledForDependency = globalSnapshot.endpoints[clusterName]?.endpointsList
229+
?.any { e -> trafficSplitting.zoneName == e.locality.zone }
230+
?: false
231+
return if (weights != null && enabledForDependency) {
232+
logger.debug(
233+
"Building traffic splitting route spec, weights: $weights, " +
234+
"serviceName: $serviceName, clusterName: $clusterName, "
235+
)
236+
WeightRouteSpecification(
237+
clusterName,
238+
routeDomains,
239+
settings,
240+
weights
241+
)
242+
} else {
243+
StandardRouteSpecification(
244+
clusterName,
245+
routeDomains,
246+
settings
247+
)
248+
}
249+
}
250+
209251
private fun getServiceWithCustomDomain(it: String): List<String> {
210252
return if (properties.egress.domains.isNotEmpty()) {
211253
properties.egress.domains.map { domain -> "$it$domain" }
@@ -217,7 +259,7 @@ class EnvoySnapshotFactory(
217259
private fun getServicesEndpointsForGroup(
218260
rateLimitEndpoints: List<IncomingRateLimitEndpoint>,
219261
globalSnapshot: GlobalSnapshot,
220-
egressRouteSpecifications: Collection<RouteSpecification>
262+
egressRouteSpecifications: List<RouteSpecification>
221263
): List<ClusterLoadAssignment> {
222264
val egressLoadAssignments = egressRouteSpecifications.mapNotNull { routeSpec ->
223265
globalSnapshot.endpoints[routeSpec.clusterName]?.let { endpoints ->
@@ -226,27 +268,30 @@ class EnvoySnapshotFactory(
226268
// endpointsFactory.filterEndpoints() can use this cache to prevent computing the same
227269
// ClusterLoadAssignments many times - it may reduce MEM, CPU and latency if some serviceTags are
228270
// commonly used
229-
endpointsFactory.filterEndpoints(endpoints, routeSpec.settings.routingPolicy)
271+
routeSpec.clusterName to endpointsFactory.filterEndpoints(endpoints, routeSpec.settings.routingPolicy)
230272
}
231-
}
273+
}.toMap()
232274

233275
val rateLimitClusters =
234276
if (rateLimitEndpoints.isNotEmpty()) listOf(properties.rateLimit.serviceName) else emptyList()
235277
val rateLimitLoadAssignments = rateLimitClusters.mapNotNull { name -> globalSnapshot.endpoints[name] }
236-
237-
return egressLoadAssignments + rateLimitLoadAssignments
278+
val secondaryLoadAssignments = endpointsFactory.getSecondaryClusterEndpoints(
279+
egressLoadAssignments,
280+
egressRouteSpecifications
281+
)
282+
return egressLoadAssignments.values.toList() + rateLimitLoadAssignments + secondaryLoadAssignments
238283
}
239284

240285
private fun newSnapshotForGroup(
241286
group: Group,
242287
globalSnapshot: GlobalSnapshot
243288
): Snapshot {
244-
245289
// TODO(dj): This is where serious refactoring needs to be done
246290
val egressDomainRouteSpecifications = getDomainRouteSpecifications(group)
247291
val egressServiceRouteSpecification = getServiceRouteSpecifications(group, globalSnapshot)
248292
val egressRouteSpecification = egressServiceRouteSpecification +
249-
egressDomainRouteSpecifications.values.flatten().toSet() + getDomainPatternRouteSpecifications(group)
293+
egressDomainRouteSpecifications.values.flatten().toSet() +
294+
getDomainPatternRouteSpecifications(group)
250295

251296
val clusters: List<Cluster> =
252297
clustersFactory.getClustersForGroup(group, globalSnapshot)
@@ -272,7 +317,6 @@ class EnvoySnapshotFactory(
272317
)
273318
)
274319
}
275-
276320
val listeners = if (properties.dynamicListeners.enabled) {
277321
listenersFactory.createListeners(group, globalSnapshot)
278322
} else {
@@ -281,11 +325,12 @@ class EnvoySnapshotFactory(
281325

282326
// TODO(dj): endpoints depends on prerequisite of routes -> but only to extract clusterName,
283327
// which is present only in services (not domains) so it could be implemented differently.
284-
val endpoints = getServicesEndpointsForGroup(group.proxySettings.incoming.rateLimitEndpoints, globalSnapshot,
285-
egressRouteSpecification)
328+
val endpoints = getServicesEndpointsForGroup(
329+
group.proxySettings.incoming.rateLimitEndpoints, globalSnapshot,
330+
egressRouteSpecification
331+
)
286332

287333
val version = snapshotsVersions.version(group, clusters, endpoints, listeners, routes)
288-
289334
return createSnapshot(
290335
clusters = clusters,
291336
clustersVersion = version.clusters,
@@ -372,8 +417,21 @@ data class ClusterConfiguration(
372417
val http2Enabled: Boolean
373418
)
374419

375-
class RouteSpecification(
376-
val clusterName: String,
377-
val routeDomains: List<String>,
378-
val settings: DependencySettings
379-
)
420+
sealed class RouteSpecification {
421+
abstract val clusterName: String
422+
abstract val routeDomains: List<String>
423+
abstract val settings: DependencySettings
424+
}
425+
426+
data class StandardRouteSpecification(
427+
override val clusterName: String,
428+
override val routeDomains: List<String>,
429+
override val settings: DependencySettings,
430+
) : RouteSpecification()
431+
432+
data class WeightRouteSpecification(
433+
override val clusterName: String,
434+
override val routeDomains: List<String>,
435+
override val settings: DependencySettings,
436+
val clusterWeights: ZoneWeights,
437+
) : RouteSpecification()

envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotProperties.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ class LoadBalancingProperties {
143143
var regularMetadataKey = "lb_regular"
144144
var localityMetadataKey = "locality"
145145
var weights = LoadBalancingWeightsProperties()
146+
var trafficSplitting = TrafficSplittingProperties()
146147
var policy = Cluster.LbPolicy.LEAST_REQUEST
147148
var useKeysSubsetFallbackPolicy = true
148149
var priorities = LoadBalancingPriorityProperties()
@@ -154,6 +155,18 @@ class CanaryProperties {
154155
var headerValue = "1"
155156
}
156157

158+
class TrafficSplittingProperties {
159+
var zoneName = ""
160+
var serviceByWeightsProperties: Map<String, ZoneWeights> = mapOf()
161+
var secondaryClusterPostfix = "secondary"
162+
var aggregateClusterPostfix = "aggregate"
163+
}
164+
165+
class ZoneWeights {
166+
var main = 100
167+
var secondary = 0
168+
}
169+
157170
class LoadBalancingWeightsProperties {
158171
var enabled = false
159172
}

0 commit comments

Comments
 (0)