-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support fine-grained cluster connectivity and multiple cable drivers #4
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.idea |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,3 +10,6 @@ line-length: | |
no-inline-html: | ||
allowed_elements: | ||
- span | ||
|
||
no-duplicate-header: | ||
allow_different_nesting: true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
# Support fine-grained cluster connectivity and multiple cable drivers | ||
|
||
## Summary | ||
|
||
Submariner currently assumes full mesh by default and provisions VPN connections between all participating clusters. For | ||
some use cases, it makes sense to offer finer-grained connectivity options. One such use case is a client-server scenario | ||
where multiple client clusters need to access a server cluster but the user does not want the client clusters to connect | ||
to one another. Finer-grained connectivity could also improve overall scalability where full mesh is not required. | ||
|
||
Submariner supports several cable driver implementations (libreswan, wireguard et al) however only one can be | ||
configured within the same cluster set, that is all clusters that join a broker must use the same cable driver. | ||
|
||
There are use cases that involve supporting a mix of cable drivers or cable driver configurations within the same | ||
maryamtahhan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
cluster set. For example, a cluster set could contain three clusters, two that are on-premise and the other | ||
in the cloud. Connections to the cloud cluster will need to use encryption while the on-premise clusters can use | ||
unencrypted connections, as they’re connected by a private link, to avoid the performance overhead of encryption. | ||
This could be achieved using different cable driver implementations or the same cable driver implementation with | ||
mkolesnik marked this conversation as resolved.
Show resolved
Hide resolved
|
||
differing encryption configurations. | ||
|
||
## Proposal | ||
|
||
This enhancement proposes a mechanism to allow users to specify their intent as to which clusters to connect and how to | ||
connect them. The default would still be a full mesh with an homogeneous cable driver. | ||
|
||
The standard mechanism in Kubernetes to specify intent is by creating a Kubernetes API resource whose effect is eventually | ||
realized into the cluster's desired state. To extend the Kubernetes API, one could either use the generic Kubernetes | ||
`ConfigMap` or use a custom resource definition (CRD). There's pros and cons to each but a CRD seems more suitable for | ||
this enhancement. | ||
|
||
The finer-grained connectivity and multiple cable driver use cases could be enabled via the same API or separate ones. | ||
Having one API may be a bit simpler in some respects but it's not foreseen that both use cases would overlap and they | ||
could interfere with one another and yield undesired results if the user isn't careful as the semantics differ. Therefore | ||
two APIs with some similar characteristics will be proposed. | ||
|
||
### Finer-grained connectivity API | ||
|
||
This following CRD is proposed: | ||
tpantelis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```Go | ||
type ClusterConnectivityPolicy struct { | ||
metav1.TypeMeta `json:",inline"` | ||
metav1.ObjectMeta `json:"metadata,omitempty"` | ||
|
||
Spec ClusterConnectivityPolicySpec `json:"spec"` | ||
} | ||
|
||
type ClusterConnectivityPolicySpec struct { | ||
// LeftClusterSelector identifies the Cluster resources representing the clusters on one end of a connection. An empty | ||
// selector indicates wildcard, ie matches any cluster. | ||
LeftClusterSelector metav1.LabelSelector `json:"leftClusterSelector,omitempty"` | ||
|
||
// RightClusterSelector identifies the Cluster resources representing the clusters on the other end of a connection. | ||
// An empty selector indicates wildcard, ie matches any cluster. | ||
RightClusterSelector metav1.LabelSelector `json:"rightClusterSelector,omitempty"` | ||
} | ||
``` | ||
|
||
The `ClusterConnectivityPolicy` CR defines the criteria for selecting which cluster pairs can connect. To resolve the | ||
connectivity policy for a remote cluster's `Endpoint`, Submariner would search for a policy whose left and right label | ||
selectors match the corresponding `Cluster` resources of the local and remote `Endpoints`. If any matching policy is found, | ||
the clusters are connected, otherwise they are not. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So silly questions, will gateways respond to changes in the policies? so for example if the selectors for an existing policy modified/extended what happens? or do we just assume this is a new policy... I guess it's not entirely clear to me what happens to the clusters (more specifically what they connect to) if no matching policies are found - is this an error/should they fall back to a default... In other words is it implicit that any clusters that don't meet the an defined policies will fall back to full mesh connectivity with any other clusters that don't meet the selector policy There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's been quite a while since I looked at this - trying to re-familiarize... If there's no matching policy for a particular remote Endpoint, it won't create a connection. Does that answer your question? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok so then if you are using the fine grained cluster connectivity it's up to you to make sure that there are no dangling clusters - in other words you need to define enough policies to make sure you avoid the situtation, there are no preset defaults There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By dangling cluster I assume you mean one that isn't included in a policy. Since the policies are a whitelist then such a cluster is naturally excluded from connectivity. The preset default is full mesh connectivity. Being a whitelist, technically the absence of any policy should mean no connectivity but we can imply a wildcard policy out-of-the-box even if we don't explicitly create it. The other alternative is to use a blacklist approach but that doesn't seem suitable. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah that's what I meant by dangling cluster... what got me thinking about it was if you had 7 clusters and 5 of those matched the policy (you definitely have 5 connected clusters) but the question I had then was regarding the 2 remaining clusters, are they automatically connected... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Referring back to the example use cases outlined above, the purpose is to allow the user to restrict which remote clusters a cluster can access, for security, scalability etc. Connectivity is a binary decision and thus a prioritized policy list with fallback is not suitable, as it is with the cable engine config.
Yes but I wouldn't say preclude - by defining policies the users is choosing specifically which clusters to connect. Let's say a user defines policies A and B. The algorithm would cycle thru the policies - first check policy A; if the clusters match the selectors then connect them; otherwise check B. If neither match then they're not connected. Now if the user added a wildcard policy C then every cluster pair would automatically match and thus be connected. This wouldn't be desirable. Perhaps there's a use case where a user has 50 clusters and wants all to interconnect except, say 3. A whitelist policy would be cumbersome so we could introduce blackbox policies as well to facilitate this scenario, in which case a whitelist wildcard policy would be relevant. This is my understanding of the problem domain and how to solve it. Perhaps you have a different idea or something I'm missing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry to reopen the the conversation, but migrating from no policy (full-mesh connectivity) to policy (no connectivity by default) seems problematic. If I have a cluster set of several clusters connected full-mesh that are sharing services, and I want to add a satellite cluster that will connect only to the geographically nearest of the existing clusters, I would want to define a policy for the new satellite cluster without affecting the existing mesh. There does not seem to be a way to get from one configuration to the other without affecting connectivity during the transition. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I'm sure there's various other use cases. It's open for ideas. What would you suggest? Perhaps adding blacklist policies? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was originally thinking about this from the perspective of selective service import and wondering how we might establish connectivity only where it is needed for the declared service consumption. That's what started me considering it from the perspective of a single cluster. I will explore whether we can define the deny/allow default on a per cluster basis and see if I can come up with something worth proposing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think you could handle that scenario with a backlist policy. Say the satellite cluster is labeled with "satellite" and the other with "east", then create a backlist policy with label selectors satellite and !east - this would disallow satellite connecting with any other cluster except for east. With no whitelist policies defined, it would default to the all-inclusive wild card policy which would allow everything else. |
||
|
||
If there are no policies defined, Submariner would default to full mesh connectivity. | ||
maryamtahhan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
`ClusterConnectivityPolicy` resources would be created and maintained on the broker cluster and synced to each participating cluster. | ||
|
||
#### Examples | ||
|
||
- We only want certain clusters to access a database server in another cluster and no other clusters to connect to one another. We | ||
label the server and client `Clusters` appropriately and define a single policy with corresponding label selectors. Since there's | ||
no other policy to match client cluster pairs, they will not be connected. | ||
|
||
```yaml | ||
apiVersion: v1 | ||
kind: ClusterConnectivityPolicy | ||
metadata: | ||
name: client-server-policy | ||
spec: | ||
leftClusterSelector: | ||
database-role: server | ||
rightClusterSelector: | ||
database-role: client | ||
``` | ||
|
||
- We want any cluster to access a database server in another cluster but no other clusters to connect. We label the server | ||
`Cluster` appropriately and define a single policy with the corresponding label selector and an empty selector to match all. | ||
|
||
```yaml | ||
apiVersion: v1 | ||
kind: ClusterConnectivityPolicy | ||
metadata: | ||
name: client-server-policy | ||
spec: | ||
leftClusterSelector: | ||
database-server: true | ||
rightClusterSelector: | ||
``` | ||
|
||
### Multiple cable drivers API | ||
|
||
This following CRD is proposed: | ||
|
||
```Go | ||
type ClusterConnectionPolicies struct { | ||
metav1.TypeMeta `json:",inline"` | ||
metav1.ObjectMeta `json:"metadata,omitempty"` | ||
|
||
Spec ClusterConnectionPoliciesSpec `json:"spec"` | ||
} | ||
|
||
type ClusterConnectionPoliciesSpec struct { | ||
Policies []ClusterConnectionPolicy `json:"policies,omitempty"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having a singleton resource with a list seems like a bad API going forward, we can solve any conflicts between competing policies with other mechanisms such as a ConfigMap that specifies priority order for cable drivers (with fall back to hard coded default if it's empty or unspecified), or giving the policies a weight such that two competing policies are judged by it and the higher value one is selected. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume a singleton ConfigMap? I assume that would have a list in priority order? How would that different than this? Another approach is using magic weight/priority numbers assigned to each policy by the user. That has disadvantages as outlined elsewhere in this PR. |
||
} | ||
|
||
type ClusterConnectionPolicy struct { | ||
// LeftClusterSelector identifies the Cluster resources representing the clusters on one end of a connection. An empty | ||
// selector indicates wildcard, ie matches any cluster. | ||
LeftClusterSelector metav1.LabelSelector `json:"leftClusterSelector,omitempty"` | ||
|
||
// RightClusterSelector identifies the Cluster resources representing the clusters on the other end of a connection. | ||
// An empty selector indicates wildcard, ie matches any cluster. | ||
RightClusterSelector metav1.LabelSelector `json:"rightClusterSelector,omitempty"` | ||
|
||
CableDriver *CableDriverSpec `json:"cableDriver,omitempty"` | ||
} | ||
|
||
type CableDriverSpec struct { | ||
// Name of the cable driver implementation. | ||
Name string `json:"name"` | ||
|
||
// Options specifies key/value configuration parameters for the cable driver. | ||
Options map[string]string `json:"options,omitempty"` | ||
maryamtahhan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
``` | ||
|
||
The `ClusterConnectionPolicies` CR defines a list of policies that are applied in order of precedence to select the cable driver | ||
and associated optional configuration options to use for each cluster connection. To resolve the connection policy for a remote | ||
cluster's `Endpoint`, Submariner would iterate the list of policies to search for a policy whose left and right label selectors | ||
match the corresponding `Cluster` resources of the local and remote `Endpoints`. The first matching policy is used. If no matching | ||
policy is found, the default cable driver is used. | ||
|
||
Defining the policies as an ordered list allows the user to resolve ambiguity should a cluster pair match multiple policies. | ||
Otherwise, if Submariner encountered such a case, it would either have to arbitrarily pick a policy or reject all of them and | ||
use the default. Neither option is ideal. An alternative to the ordered list would be to have separate resources for each policy | ||
maryamtahhan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
and define a priority or precedence field. However it seems a single resource without the use of magic priority numbers would be | ||
easier for the user to maintain. | ||
Comment on lines
+145
to
+146
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I disagree with this premise, using weights won't have any tangible disadvantage versus using a singleton resource. Using the ordered list doesn't give any better clarity as to which policies affect what since you're lacking the selector part when viewing it, and as an admin would have to query the individual policies for that data anyhow. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess it's a matter of opinion/viewpoint. There's pros and cons to both approaches - we would just need to pick one. |
||
|
||
The `ClusterConnectionPolicies` CR would be a singleton with a well-known name and would be maintained on the broker cluster | ||
and synced to each participating cluster. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be good for debugging purposes if the status field of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree. Moreover we should consider adding a way to get statuses of all endpoints or alternatively get a complete view on compatible or not compatible cluster's communication's policy. This can maybe be logged instead of a full feature. This can be a good debug tool as well |
||
#### Examples | ||
|
||
- We want to use a specific cable driver to connect on-premise clusters. We define a policy to select clusters labeled as | ||
`on-premise` to use a `vxlan` cable driver. The default cable driver will be used to connect everything else. | ||
|
||
```yaml | ||
apiVersion: v1 | ||
kind: ClusterConnectionPolicies | ||
metadata: | ||
name: cluster-connection-policies | ||
spec: | ||
policies: | ||
- leftClusterSelector: | ||
location: on-premise | ||
rightClusterSelector: | ||
location: on-premise | ||
cableDriver: | ||
name: vxlan | ||
``` | ||
|
||
- We want to use a specific cable driver to connect on-premise clusters and another cable driver to connect everything else. | ||
We define a policy to select clusters labeled as `on-premise` to use a `vxlan` cable driver and a fall-back policy that selects | ||
all other clusters to use `wireguard`. | ||
|
||
```yaml | ||
apiVersion: v1 | ||
kind: ClusterConnectionPolicies | ||
metadata: | ||
name: cluster-connection-policies | ||
spec: | ||
policies: | ||
- leftClusterSelector: | ||
location: on-premise | ||
rightClusterSelector: | ||
location: on-premise | ||
cableDriver: | ||
name: vxlan | ||
- leftClusterSelector: | ||
rightClusterSelector: | ||
cableDriver: | ||
name: wireguard | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm really not sure why these two different proposals are being bundled together.
The ability to support different topologies (with or without connectivity between participating clusters) is not directly related to being able to specify different cable drivers between any two connected clusters.
I suggest these two be split out and each focus on their own problem domain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the one hand, this could be separated into two proposals. But they share a common foundation which is defining the attributes of cluster adjacencies. My suggestion would actually be to align them more closely and use the same policy CRD for defining adjacencies and their cable properties.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there was an original iteration where they were combined but then we decided to separate them. There are different use cases for the what vs the how.