Skip to content

Multi-cluster Discovery Proposal #10747

Closed
@rushi47

Description

@rushi47
tags: linkerd kubernetes service mesh multicluster

Multi-cluster Discovery Proposal

LFX Mentorship proposal By Rushikesh Butley & Matei David

Problem :

Linkerd's multicluster extension allows operators to export headless services, which are primarily used for workloads deployed as StatefulSets (traditionally databases).

Working with a StatefulSet in complex cluster topologies (three or more clusters) is unwieldy. It requires constant manual intervention from cluster operators and application owners. For each new (or removed) exported service, the StatefulSet has to be edited, and all of the workloads have to be redeployed.

The above problem makes endpoint discovery for stateful workloads hard to deal with.

Goals & Constraints :

For this proposal below are the goals and some of the constraints, which we have scoped out.

  • Goals :

    • Create global service in each linked cluster, and make sure it aggregates all the mirrored services.
    • Develop a reference architecture that can be used in subsequent tests and investigations.
    • Document what the problem is and how the solution works.
    • Manually validate the solution.
  • Constraints :

    • Implementation should be a prototype, separate from the Linkerd repo.
    • Implementation is done by the start of June.
    • Will not be part of multicluster official extension.
    • Will not focus on any UX through CLI.

How should the problem be solved?

Design Exploration :

Background :

Currently, in mutlicluster discovery if there are multiple peers, when service gets mirrored from target cluster to source , for each peer mirror service is created. To elaborate, below is the example :

We start with two clusters named source and target. Let's assume that there is service name foo existing in source and target. And it might need to discover all other replicas existing in target/

As we can imagine, there are various applications like which does DNS based service discovery for various purposes including but not limited to Statefulsets for operating in HighAvailability. Ex. Thanos, Nginx Upstreams etc.

Background for this proposal :

While it works fine for cluster with one target, it will be quite cumbersome issue, when there are more than one target cluster. To solve this problem, we want standalone service existing in cluster, which when queried, can return all the Endpoints from it's peered cluster.

Solution :

Global Service :

Possible solution to this problem is, creating global service in each linked cluster. This global service will act as aggregator and will have all the mirrored EndpointSlices. Below is the example to clarify it further.

Once the proposal is implemented below will be the view for source cluster.

With Current Release

currentRelease

Proposed Solution

comingRelease

Summary :

In summary, if a Statefulset workload is deployed in a source cluster, and it needs to discover replicas from other clusters, each cluster will need to be linked against the source. Each individual headless service will be mirrored in the source cluster. This leads to operational problems, where a statefulset needs to be rolled out and manually modified each time a new service is added. Consider the following example where a single target cluster is linked against source:

- name: foo
  args:
     - join
     - foo,foo-target1

Linking another cluster leads to a manual intervention to add the new service:

- name: foo
   args:
     - join
     - foo,foo-target1,foo-target2

Implementation Ideas :

To achieve the above solution using EndpointSlices, we might start building on service mirror component. We will deploy controller in each cluster, alongside current linkerd services. Controller will keep watching all the local services inside cluster.

To break it down into steps :

  1. As we discussed above, will create the new Service suffixed by -global.

  2. In source cluster, we can have controller/operator, which keep looking for the new Service being added. To filter the services, controller will be filtering on the label mirror.linkerd.io/mirrored-service: "true", this annotation is already place by Service Mirror component, when it mirrors service in source cluster.

  3. This will help us, executing our logic when new service with respective anotation appears in our cluster.

  4. Once our controller receives any event for this filter, controller if there is respective global service exist for mirrored service in our cluster by the suffix -global.

    a) If it doesn't exist first we create Headless Service using our controller. And name it by snapping -global as suffix at the end of mirror service name.

    b) If it exist, we skip creating Headless Service.

  5. To find respective synthetic endpoint for mirror service existing in our cluster, we query our K8s registery for Service's respective Endpoints.

  6. All the retrieved Endpoints for mirrored services, will be used to create new EndpointSlice and it will be owned by the -global service . We will start with maintaining only one EndpointSlice and if required we can split it/scale it later.

Development Plan along with Testing Strategy :

We can deliver above feature in two phases.

  • Phase 1 :

    • In first phase we can focus on writing Controller/or refactoring Service Mirror, which look for new Services being added with label : mirror.linkerd.io/headless-mirror-svc-name

    • Create Headless Service with the name suffixed with -global from the initial service which is mirrored.

    • Testing Strategy : To test this phase, we can create Multicluster topolgy of 3-4 cluster. And make sure that only One Global Service is being created per cluster, respective to each type of unique Service being mirrored.

  • Phase 2 :

    • Once we have Headless Service ready from the Phase 1, in this phase we create only one EndpointSlice (Maybe we can scale it later if required).
    • And we add respective synthetic endpoints from mirrored service in this EndpointSlice.
    • We also check for any changes, to the respective endpoints and add/remove them accordingly.
    • Testing Strategy : To test this phase, we can query the Headless Service created in Phase 1 and make sure that all the EndpointsSlices being returned match against the synthetic endpoints create by Service Mirror.

How we will run it ? :

We will build docker images of our controller, and deploy these images in our multicluster environment using helm charts. It will monitor all the local Services inside respective cluster. It will take required action when encountered label of interest.

For testing, we will be simply running controller against local kubernetes cluster with default config.

Open Questions :

  • Even after creating this Service Discovery mechanism, we need to make sure that consuming service has capablity to handle multiple ips return by the Service endpoint.

  • From my intial assumptions, I dont think there will be any change in proxy. As the request will still be handled in the same way, currently being handled.

  • Is it best bet to rely on Service Mirror, for the base implementation ? And write this as seprate controller. Or is it better to refactor the Service Mirror code. From my thoughts we should go with former one.

Language of Choice
  • I would like to propose writing this Controller in Rust instead of GoLang. As I think with Go lang it will be alot more boilerplate code.
  • With rust it will be quite fast and efficient, to write new Kubernetes Controller as there will be lot less code.
  • Because of strict type system, interacting with types will be quite explicit and error handling will be lot more easy.
  • As it's Rust, we will get out of the box performance, along with very less cpu and memory footprint.
  • Also I am quite keen about learning Rust, this will be very good opportunity for me to write some hands on code. And learn from the best :).

Would you like to work on this feature?

yes

We have started the initial development here : https://github.com/rushi47/service-mirror-prototype

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions