Light Dark Auto

Greymatter Specification Language

Introduction to Greymatter Specification Language

Greymatter Specification Language, GSL for short, is a declarative domain specific language designed for application networking in a modern mesh-like topology. GSL reduces boilerplate by providing natural object relationships and drop-in customization. GSL empowers users from across the experience spectrum to make complex configuration changes rapidly and with confidence.

The CUE language forms the foundation of GSL. CUE is a data configuration language with roots to both the field of linguistics and to internal Google projects. This gives CUE a unique set of advantages compared to other data configuration languages. Namely, it was birthed from environments steeped in extreme complexity and scale. If you want to read more about the origins or technical details, we recommend the official CUE documentation.

CUE grants GSL powerful features including:

  • blazing fast schema validation and versioning
  • boilerplate reduction
  • improved but familiar JSON-like syntax
  • unique configuration philosophies

To clarify, GSL is not a separate language or an offshoot of CUE. Rather, it is a CUE package that describes a complex set of data constraints and relationships which model the greymatter control plane. As a result, all CUE constructs extend into GSL.

The Basics of GSL

This section provides a high-level overview and description of GSL. If you are a more hands-on learner or want to get started immediately, we recommend circling back on this page and starting with the GSL getting started tutorial.

The basics of GSL lie in a working knowledge of CUE. We won’t explicitly cover CUE topics, so we recommend reading the CUE documentation before trying to understand GSL. You are free to skip it however, as we will point out any usage of intermediate or advanced CUE and CUE’s basic workings are intuitive for those with a programming background.

Project Organization

GSL configurations are organized into projects. A project maps to a single tenant namespace. Each project contains the configurations for all the tenant’s services. GSL projects are bootstrapped by the greymatter init command.

// a tree listing of a GSL project

cue.mod
k8s
├── manifests.yaml
└── sync.yaml
greymatter
├── core
   └── edge.cue
├── globals.cue
├── application
   └── application.cue
   └── application-database.cue
└── policies

The project stores application configurations underneath the greymatter folder with every application receiving its own folder. An application’s folder contains the service files for the service and any direct dependencies. A direct dependency would include databases or separate services that are used only by that service and would never exist independently. One service file corresponds to only one service.

GSL defines two types of service files. Special ‘edge’ services and normal services. Edge services refer to the tenant application edge node. This is a standalone proxy that acts as the entrypoint for the project. Normal service types correspond to the applications that tenants deploy and manage.

Outside of the GSL service files, GSL also specifies a globals files for variables shared across the project, and a policies folder which can contain any number of custom CUE configurations. GSL enforces a schema but also allows for powerful customization. Policies are abstracted CUE definitions or data that tenants can write to further optimize and simplify their application networking configurations. Policies are an advanced part of GSL, but if you want to read more about them, see this guide.

The GSL service file

Top Level Definitions

As we mentioned in the previous section, GSL defines two types of service files: edge and service. Every file must use one and only one type. To declare a service file as one type, use the corresponding GSL definition.

The gsl.#Edge definition declares the file as a greymatter edge type. Only the greymatter application edge node created by greymatter init or extra edge nodes you create should implement this definition.

The gsl.#Service definition declares the file as a normal service. All the tenant’s deployed services will use the service definition. It both asserts a configuration schema and injects configuration automatically to help reduce boilerplate or to simplify complex yet crucial features.

Defining the service definition looks like this:

Thing: gsl.#Service {
...
}

Application Networking Object Hierarchy

The application networking object hierarchy refers to the collection of configuration objects stacked on top each other to form a complete end-to-end request handling pipeline. Listed from parent to child, they are:

  1. Listeners
  2. Routes
  3. Upstreams

The genesis of the hierarchy is the gsl.#Service or the gsl.#Edge definition.

Each parent object has a one-to-many relationship with its children, starting with the gsl.#Service definition. In other words, the service can have multiple listeners which can have multiple routes which can have multiple upstreams. By combining all these elements, we can express complex networking principles while enforcing human-readable models.

The next sections provide an overview of each type of object and a simplified example. For guidance on how to actually construct each object, either follow the getting started with GSL tutorial or refer to each object’s reference page linked in the section.

Listeners

Listeners instruct the proxy to listen for connections on a specific network interface and port. There are two types of network interfaces: ingress and egress. Ingress listeners only accept requests originating from outside the local machine and egress listeners only accept requests originating from inside the local machine.

Regardless of whether they are ingress or egress listeners, listeners can listen for two types of connections: HTTP and raw TCP. HTTP listeners use the gsl.#HTTPListener definition whereas TCP listeners use the gsl.#TCPListener definition. Every listener must have one or the other.

Defining a listener on a service looks like this, where listener1, listener2, and listener3 are arbitrary names:

gsl.#Service & {

    ingress: {
        "listener1": {
            gsl.#HTTPListener
        }
        "listener2": {
            gsl.#HTTPListener
        }
    }

    egress: {
        "listener3": {
            gsl.#TCPListener
        }
    }
}

Routes

Routes define matching criteria a listener will use to forward incoming requests. These are only allowed from within a gsl.HTTPListener. When a connection matches one of the routes, the listener will first pass the request through its filter chain and then perform any route prefixing, route rewriting, redirects, or retries that are specified by the route configuration. The proxy finally sends it to the connected upstream.

Setting a route on a listener looks like this:

gsl.#Service & {
	ingress: "listener1": {
        gsl.#HTTPListener

        routes: {
            "/app": { ... }
            "/app2": { ... }
            "/" : { ... }

        }
    }
}

Upstreams

Upstreams refer to the destination of a request and are the last object in the hierarchy. In #HTTPListeners they attach to routes and in #TCPListeners they attach directly onto the listener. Upstreams can either refer to their destination through a static IP or through service discovery. In the latter case, you must know the name and namespace of the service.

Creating upstreams on a #HTTPListener and a #TCPListener looks like this:

gsl.#Service & {
	ingress: "listener1": {
        gsl.#HTTPListener

        routes: {
            "/": {
                upstreams: {
                    gsl.#Upstream

                    "serviceDiscoveryName": {
                        namespace: "namespaceOfServiceDiscoveryName"
                    }
                    "staticIP": {
                        instances: [{host: "100.100.100.100", port: 80}]
                    }
                }
             }

        }
    }

    egress: "listener2": {
        gsl.#TCPListener

        upstream: {
            gsl.#Upstream

            name: "serviceDiscoveryName"
            namespace: "namespaceOfServiceDiscoveryName"
        }
    }
}

Notice how listener2 can only have a single upstream and how listener1 can address multiple upstreams. Connecting multiple upstreams to one route is useful for more advanced traffic shaping patterns like blue-green deployments.

Conclusion

This concludes the brief overview of GSL—greymatter’s answer to the configuration complexity nightmare of application networking. Hopefully, this foundation will help shore up the more hands-on learning materials on this site. Please take a look at the next section if you want to put your newfound knowledge to work.

Next Steps

To gain some hands-on experience with writing GSL to configure services, check out the getting started with GSL tutorial.

Some basic guides that might make a good first entry into GSL are: