-
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, and it was rather monolithic unit. It was also built around BackendService
and Origin
object model serialised directly from the origins.yaml file.
The result is restrictive. For this reason we have designed a new highly compassable 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"
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.
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.
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())
);
public static final Schema.FieldType SCHEMA = object(
field("routes", list(object(
field("condition", string()),
field("destination", routingObject()))
)),
optional("fallback", routingObject())
);
public static final Schema.FieldType SCHEMA = object(
field("routes", list(object(
field("prefix", string()),
field("destination", routingObject()))
))
);
public static final Schema.FieldType SCHEMA = object(
optional("pipeline", or(string(), list(string()))),
field("handler", routingObject())
);
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.