-
Notifications
You must be signed in to change notification settings - Fork 79
Styx Object Model
Styx application routing was originally designed around its origin configuration file structure. It was built around BackendService
and Origin
object model serialised directly from the origins.yaml file.
It was quite monolithic and not flexible for further changes. For this reason we have designed a new highly composable object model to specify how Styx routes incoming requests. Some of the early phases of the work is documented here: Advanced Routing Configuration.
As of writing (1.0.0.beta8 release) the new object model is still experimental. Therefore Styx Core contains both the old monolithic application routing engine and the new routing object engine.
Styx Core consist of 3 kinds of entities:
- Data Plane entities filter and route incoming requests to appropriate backend services.
- Control Plane entities that monitor and configure data plane objects. Such as health check monitor.
- Servers, listen to incoming network traffic, and pass them on to data plane.
Styx has a common configuration object framework that covers all these 3 entities. It provides a standard configuration format, validation, loading, storage, and lifecycle management.
A Styx configuration object has:
- An unique name (among similar "entities")
- Type
- Tags
- Configuration (in yaml)
routingObjects:
root:
type: PathPrefixRouter
config:
routes:
- { prefix: /, destination: landingApp }
- { prefix: /api/, destination: apiServer }
landingApp:
type: LoadBalancingGroup
config:
origins: landing
apiServer:
type: LoadBalancingGroup
config:
origins: apiServer
landing01:
type: HostProxy
tags:
- landing
config:
host: "remotehost01:443"
landing02:
type: HostProxy
tags:
- landing
config:
host: "remotehost02:443"
apiServer01:
type: HostProxy
tags:
- apiServer
config:
host: "remoteapihost01:443"
apiServer02:
type: HostProxy
tags:
- apiServer
config:
host: "remoteapihost02:443"
Control plane objects are long running processes that configure and monitor the data plane objects, but won't participate in data plane processing. They are used for
- Configuring the data plane from external configuration sources.
- Monitoring and health checking.
- Exposing metrics and information.
See Control Plane Object Reference for detailed information.
Styx data plane is a directed acyclic graph of named rouging objects. Each in some way processing the request passing through. Request messages enter the data plane typically at the root object (but can be any other named object) and they trickle down some path towards the leaf. Response messages follow the same path, but from leaf to root.
The data plane is declared in the routing object configuration.
There are two ways to specify relationships between the objects: 1) a named reference or 2) embedding an anonymous object.
The previous configuration example demonstrates named references. Each object is named, and the forwarding graph is formed by the named references between the objects. The previous configuration can be illustrated as:
PathPrefixRouter
| +--- HostProxy
| "/" | (tag: landing)
+------------> LoadBalancingGroup ------+
| app: landing |
| +--- HostProxy
| (tag: landing)
|
|
| "/api/"
+------------> LoadBalancingGroup ------+--- HostProxy
app: apiServer | (tag: apiServer)
|
+--- HostProxy
(tag: apiServer)
We could also embed the two LoadBalancingGroup
objects in PathPrefixRouter
. Like so:
routingObjects:
root:
type: PathPrefixRouter
config:
routes:
- prefix: /,
destination:
type: LoadBalancingGroup
config:
origins: landing
- prefix: /api/,
destination:
type: LoadBalancingGroup
config:
origins: apiServer
...
By embedding the LoadBalancingGroup
objects, we have eliminated them from the data
plane graph. The relevant functionality is now appears in the root PathPrefixRouter
.
The DAG thus becomes:
PathPrefixRouter
| +--- HostProxy
| "/" (app: landing) | (tag: landing)
+-----------------------------+
| |
| +--- HostProxy
| (tag: landing)
|
| "/api/" (app: apiServer)
+-----------------------------+--- HostProxy
| (tag: apiServer)
|
+--- HostProxy
(tag: apiServer)
We have implemented a "backwards compatibility mode" allowing existing deployments to keep origins.yaml configuration with the new routing object engine (PR #458). It translates the origin file automatically in this routing object configuration.
See Routing Object Reference for detailed information.
Balances incoming traffic using Power of 2 algorithm among all group members.
Schema:
val SCHEMA = `object`(
field("origins", string()),
optional("originRestrictionCookie", string()),
optional("stickySession", `object`(
field("enabled", bool()),
field("timeoutSeconds", integer())
))
)
Any named routing object can become a member regardless of type. The group membership is indicated by origins
field, which is a non-empty string. Any object appropriately tagged with origins
value automatically becomes a member. Suppose origins
is green
. Then any object tagged with lbGroup=green
automatically becomes a group member.
A routing object can be tagged with state=active
, state=unreachable
or state=inactive
tags. A group member with state
receives traffic only if it is active
. It assumed active
if the state
tag is absent.
-
state=active
means the object is administratively enabled, and reachable. -
state=unreachable
means the object is administratively enabled, but declared unreachable by the health check function. -
state=inactive
means the object is administratively disabled.
Relays traffic to the configured remote host.
public static final Schema.FieldType SCHEMA = object(
field("host", string()),
optional("tlsSettings", object(
optional("trustAllCerts", bool()),
optional("sslProvider", string()),
optional("trustStorePath", string()),
optional("trustStorePassword", string()),
optional("protocols", list(string())),
optional("cipherSuites", list(string())),
optional("additionalCerts", list(object(
field("alias", string()),
field("certificatePath", string())
))),
atLeastOne("trustAllCerts",
"trustStorePath",
"trustStorePassword",
"protocols",
"cipherSuites",
"additionalCerts")
)),
optional("connectionPool", object(
optional("maxConnections", integer()),
optional("maxPendingConnections", integer()),
optional("connectTimeoutMillis", integer()),
optional("socketTimeoutMillis", integer()),
optional("pendingConnectionTimeoutMillis", integer()),
optional("connectionExpirationSeconds", integer()),
atLeastOne("maxConnections",
"maxPendingConnections",
"connectTimeoutMillis",
"socketTimeoutMillis",
"pendingConnectionTimeoutMillis",
"connectionExpirationSeconds")
)),
optional("responseTimeoutMillis", integer()),
optional("metricPrefix", string())
);
This object is always a terminal object in the data path. It cannot pass the traffic any further. It always produces a response.
Performs a routing decision based on some condition.
public static final Schema.FieldType SCHEMA = object(
field("routes", list(object(
field("condition", string()),
field("destination", routingObject()))
)),
optional("fallback", routingObject())
);
See The Styx Manual for details.
Performs a routing decision based on longest URL path prefix match.
Similar to ConditionRouter but the condition is always a path prefix.
public static final Schema.FieldType SCHEMA = object(
field("routes", list(object(
field("prefix", string()),
field("destination", routingObject()))
))
);
A container for running Styx interceptors, both external plugins and built in interceptors.
public static final Schema.FieldType SCHEMA = object(
optional("pipeline", or(string(), list(string()))),
field("handler", routingObject())
);
See Styx User Manual for details.
Responds with a HTTP response.
NOTE: We are likely to rename this to StaticResponse
.
public static final Schema.FieldType SCHEMA = object(
field("status", integer()),
optional("content", string()),
optional("headers", list(object(
field("name", string()),
field("value", string())
))));
This is a terminal object that cannot pass the request any further. Guaranteed to always respond with given response parameters.
Monitors a group of routing objects. The group membership follows the LoadBalancingGroup
configuration: the objects=GROUPNAME
field identifies the group name, and the objects tagged with lbGroup=GROUPNAME
are included in the monitoring group.
Schema:
val SCHEMA = SchemaDsl.`object`(
field("objects", string()),
optional("path", string()),
optional("timeoutMillis", integer()),
optional("intervalMillis", integer()),
optional("healthyThreshold", integer()),
optional("unhealthyThreshold", integer())
)
The health check monitor is intended for probing HostProxy
objects (because they communicate over the network). But as with LoadBalancingGroup
any type of object can be a group member.
The monitor periodically probes its group members. A probe is considered successful when the underlying object responses with a successful HTTP status code (less than 400).
It tags the healthy group members with state=active
, and unhealthy or unreachable members with state=unreachable
.
The Lifecycle Tags section below provides more information about health check provider object tagging policy.
Configures Styx data plane from Styx origins yaml configuration.
val SCHEMA = SchemaDsl.`object`(
field("originsFile", string()),
optional("monitor", bool()),
optional("ingressObject", string())
Reads the origins file from location given by originsFile
field. It constructs a graph of routing objects corresponding to the yaml file and injects them to the styx object database:
- Creates a
HostProxy
object for each origin. - Creates
LoadBalancingGroup
for each BackendService. - When health checking is enabled, starts a
HealthCheckMonitor
for eachLoadBalancingGroup
. - Creates a
PathPrefixRouter
that is the root of the resulting object graph.
The optional ingressObject
gives a name to the root PathPrefixRouter
object. When the ingressObject
is absent, the root object is called <service-name>-router
.
You still need to configure Styx to pass the received traffic to the root object (by setting the httpPipeline
).
You can configure multiple YamlFileConfigurationService objects, one for each origins file. But you will need to take care to avoid name clashes for the generated objects.
When monitor
is true, the service automatically monitors for configuration changes, and reconfigures the data plane accordingly.
Tags are used to record state and health information about routing objects, in particular about HostProxy
objects.
The state
tag can have the following values:
-
active
- The object is running normally, and can be included in the rotation of a LoadBalancer object. -
inactive
- The HostProxy has been deactivated via the Admin interface, and is not included in the rotation of a LoadBalancer object. -
unreachable
- A HealthCheck service has determined that this HostProxy's downstream origin is not reachable. The object is not included in the rotation of a LoadBalancer.
The state
tag is always present, and always has a value, on a HostProxy object.
If a HealthCheck service is configured on a HostProxy object, that object will start up in the unreachable
state. It is the responsibility of the HealthCheck service to determine that the associated origin is reachable, and to change the HostProxy state to active
.
If no HealthCheck service is configured on a HostProxy object, that object will start up in the active
state.
A HealthCheck service may change the state tag value of a HostProxy object from active
to unreachable
, and from unreachable
to active
.
A request via the Admin interface can change the state tag value of a HostProxy object from either active
or unreachable
to inactive
. If a HealthCheck service is configured, activating the object via the Admin interface will change the state from inactive
to unreachable
. If no HealthCheck service is configured, activating the object via the Admin interface will change the state from inactive
to active
.
The healthCheck
tag is used by the HealthCheck service to record information about whether healthcheck probes are currently failing or succeeding via a HostProxy object. Probes that are failing on an active object, or succeeding on an unreachable object, can lead to a change in the state
tag.
The tag's value has the format on[;<condition>:<count>]
- Where an object's state is
active
and the health probes are passing, or the state isunreachable
and the probes are still failing, then the tag's value will just beon
. - Where an object's state is
active
but health probes are currently failing, then the tag's value will beon;probes-FAIL:<count>
, where<count>
is the number of consecutive failed probes. When this count reaches a configured threshold, the HealthCheck service will change thestate
tag tounreachable
. - Where an object's state is
unreachable
but health probes are currently succeeding, then the tag's value will beon;probes-OK:<count>
, where<count>
is the number of consecutive successful probes. When this count reaches a configured threshold, the HealthCheck service will change thestate
tag toactive
.
The tag is not present when health checking is not configured on an object.