Skip to content

Styx Object Model

Mikko Karjalainen edited this page Jan 3, 2020 · 15 revisions

Problem Statement

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 Object Model

Styx Core consist of 3 kinds of entities:

  1. Data Plane entities filter and route incoming requests to appropriate backend services.
  2. Control Plane entities that monitor and configure data plane objects. Such as health check monitor.
  3. 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)

Example

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"

Data Plane Objects

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.

Routing Object Reference

Load Balancing Group

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.

Host Proxy

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.

Condition Router

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.

Path Prefix Router

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()))
            ))
    );

Interceptor Pipeline

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.

Control Plane Objects

Object Reference

Health Check Monitor

Yaml File Configuration Service

Lifecycle Tags

Tags are used to record state and health information about routing objects, in particular about HostProxy objects.

The state tag

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

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 is unreachable and the probes are still failing, then the tag's value will just be on.
  • Where an object's state is active but health probes are currently failing, then the tag's value will be on;probes-FAIL:<count>, where <count> is the number of consecutive failed probes. When this count reaches a configured threshold, the HealthCheck service will change the state tag to unreachable.
  • Where an object's state is unreachable but health probes are currently succeeding, then the tag's value will be on;probes-OK:<count>, where <count> is the number of consecutive successful probes. When this count reaches a configured threshold, the HealthCheck service will change the state tag to active.

The tag is not present when health checking is not configured on an object.

Clone this wiki locally