Service topology

While roles describe the set of microservices in a servie, as well as how they are configured, by themselves they say nothing about which other roles in the service they need to relate to.

One of the requirements of a service application specification is to define how roles can talk to each other through their channels.

To produce this specification, a service must define a new entity: the connector.

Connectors

It is useful to think of a connector as a built-in, very specializaed kind of role, whose goal is to provide specific patterns of communication among channels of instances of roles connectected through the connectors.

A connector in Kumori has two ports: a client port and a server port. Those ports are not explicitly represented, instead they determine what sort of channels can connect to each port: only client channels from roles can connect to the client port of a connector. Likewise, the connector can only connect to server channels through its server port.

As of version 1.0.0 only two types of connectors are supported:

  • load balancer connectors, lb. With the simple standard load balancing semantics. Components linked to the connector throught a client channel can resolve that channel name to obtain the IP of the connector. When initiating communications through that IP, the connector spreads load evently to the set of server channels connected through its server ports.

  • complete/full connectors, full. Providing full visibility to roles with client channels connected to the client port about the set of server channels the server port connects to. A component code can find all endpoints associated to the server channels the connector is linked to by resolving its client channel name. If SRV records are retrieved, it will obtain the pair IP:port of each one of the server channels connected.

The specification of the connectors in the service manifest is simply this:

connect: [string]: as: "lb" | "full"

That is, it forms a dictionary, the keys being the names of the connectors, and the values being their type (lb or full).

Specifying the topology graph

The connectivity of roles in a service is completed by specifying the links of the graph. The topology graph is a bipartite graph, where connectors are one of the node classes and roles (actually, vsets) are the other node class.

Edges in the graph are directed and can flow only from client channels on roles to connectors or from connectors to server channels of roles.

There is an exception to the above rule, and it pertains to channels of the service application itself. As seen earlier, a service also defines (naturally) a microservice interface, with a set of client/server channels.

Edges of this graph are represented within the connect field in the service manifest, and their specification is as follows.

connect: [string]: {
  as: *"lb" | "full"
  from: [vsn=#roles]: vset[vsn].srv.#clients | [vset[vsn].srv.#clients,...vset[vsn].srv.#clients]
  to:   [vsn=#vsets]: [sr=(vset[vsn].srv.#servers | vset[vsn].srv.#duplexes)]: {
    meta: {...}
  }

  if as == "lb" {
    if len(srv.server) != 0 {
      from: ["self"]:     srv.#servers | [srv.#servers,...srv.#servers]
    }
    if len(srv.client) != 0 {
      to:   ["self"]:     [srv.#clients]: {meta: {...}, ...}
    }
  }
}

A service client channel is intended to be linked to server channels of some other service, thus, they are just a means to reach some server channel of another service. This is the reason they are considered as really server channels for all intends and purposes of specifying edges.

Likewise a service server channel will get attached to some other service client channel, thus, for all intends and purposes, from within the service itself it plays the role of a client channel.

In order to capture this "reversed" inner channel semantics of service channels, the specification uses specific rules for the special role name self, to refer to the service itself, and sets up special case rules to reverse the interpretation client/serv er of its channels.

Let us see simple example of how this specification is used. Let us consider a simple service consisting of a frontend role and a backend role.

The frontend role (well, actually the component implementing it, but that is irrelevant for the example) has just a server channel, web, and a client channel, back.

The backend role has only a server channel, myserv. Furthermore, we assume the service application exposes only a server channel, service, which is actually going to be handled by the frontend role through its web, server channel.

the way the topology would be specified is this:

connect: incoming: {
  as: "lb"
  from: self: "service"
  to: frontend: web: _
}

connect: toback: {
  as: "lb"
  from: frontend: "back"
  to: backend: myserv: _
}
given a (role,channel) pair it can only appear in the from field on at most one connector.
the formalism allows multiple connectors to link to a server channel, which also fits well the intuition that a server channel can provide its function to multiple sources of requests.

full connectors and duplex channels

In the specification given, full connectors can link to duplex channels too. This is, in fact, the only connection allowed with such type of channels and they emphasize their server aspect.

We explained that a duplex channel encompasses two aspects of the channel: a client and a server aspect. A link from a full connector to a duplex channel is akin to connecting the "client" aspect of that channel to the connector, at the same time that the connector links to the "server" aspect of the channel.

The end result is that the role with the duplex channel can resolve the name of the channel to obtain all those channels the connector has been linked to (be them duplexes or servers).

You should realize that this is just a convenience of the model to simplify expressing the usage of such type of channels, as a similar effect could be achieved by specifying a client and a server channel with the same name for the component implementing the role, linking the connetor to the server channel.

Topology and network security

One of the advantages of Kumori’s topology representation for a service is the ability to automatically deduce from it proper firewalling and routing rules. Kumori Platform, based on the connectivity represented by channels, connectors and links, determines what traffic should be allowed and what routes must be set up among all the endpoints and component instances within a service.

Kumori Platform adopts the approach of enabling only those routes that are necessary according to the topology of a service.

Traffic in and out of a deployed service

Traffic into a deployed service can only enter it through its declared server channels. Traffic out to other deployed services can only leave a deployed service through its declared client channels. Traffic to the internet is, in this version, unrestricted.

Kumori Platform provides a mechanism to enable incoming traffic to server channels in a service, and outgoing traffic through client channels: service channel linking.

By default, though, connections out to public IP addresses are not restricted when a service is deployed.

In a future release, it will be possible to fine-tune access to external IP addresses.