Skip to content
This repository has been archived by the owner on Jul 11, 2023. It is now read-only.

pkg/*: address control plane implementation gaps #4074

Merged
merged 2 commits into from
Sep 7, 2021

Conversation

shashankram
Copy link
Member

@shashankram shashankram commented Sep 2, 2021

Description:
This change refactors the core control plane components
to address several gaps in the existing implementation.

At a high level, it resolves the following:

  1. Traffic to a service with multiple ports is filtered and
    routed correctly based on the port the traffic is directed
    to (Local cluster endpoints for service with multiple ports are not correctly programmed #3777). This was previously a blocker for users with
    multiple ports/protocols per k8s service object.

  2. Builds mesh traffic policies for LDS/CDS/RDS configs
    inline consistently. This simplifies the code and makes
    it a lot easier to test config generation based on the
    state of the mesh (service, service accounts, SMI policies
    etc.). It also eliminates outbound policy merges that
    are unnecessary. Building config for different envoy configs
    inline will help when OSM moves to an event-driven config
    generation per proxy/group model, where an event can produce
    configuration using a nearly consistent view of the caches.

  3. Allows the TargetPort for a service to be different than it's
    Port value. This is common in k8s to allow app authors to
    change the backend port without needing to update their client
    code.

  4. Similar to egress, mesh RDS configs are isolated per port to
    avoid conflicts when the same host needs to be routed differently
    per port. This can happen when the same service exposes multiple
    HTTP ports that need to be routed differently.

  5. Correctly generates hostnames for a service-port combination that
    clients can use to access the service.

  6. Addresses bugs in endpoint discovery when the service has multiple
    ports. Correctly translates a cluster name to a MeshService to be
    able to retrieve its endpoints.

  7. Implements SMI TrafficSplit for permissive mode for both HTTP and
    TCP traffic.

  8. Removes and simplifies code that is not required, such as the APIs
    to retrieve port:protocol mappings for a service, and other redundant
    APIs.

  9. E2e tests and unit tests for various scenarios: multiple ports per
    service, permissive mode traffic split, different TargetPort for
    service Port etc.

To understand this change better, it's important to note the following
fundamental changes made to address the existing implementation issues
noted above.

  • A MeshService now carries information on the port, target port and
    protocol associated with the service. The implementation ensures
    the target port is always set for a service with an endpoint. It is
    necessary to disambiguate between the two because k8s allows routing
    traffic to a service whose Port and TargetPort are different. This
    is of significant importance when it comes to filtering and routing
    traffic on the client and server side. A k8s service with multiple
    ports is transposed to multiple MeshService representations within
    the OSM control plane. A MeshService is the source of truth while
    creating filtering and routing configs in Envoy as opposed to a k8s
    service. In other words, filter chains, clusters per service have
    a 1-1 mapping to a MeshService instance.

  • RDS mesh inbound and outbound route configurations are built per
    port, similar to egress route configurations. This ensures there
    can be no conflicts when a service/FQDN needs to be routed differently
    based on the port.

  • Endpoints for a MeshService is filtered by its TargetPort when set. This
    is required to ensure endpoints not associated with the MeshService are not
    programmed for upstream clusters. This is required to support multiple
    ports per service.

Part of #4052
Resolves #3777
Resolves #2527

Testing done:

Affected area:

Functional Area
Control Plane [X]

Please answer the following questions with yes/no.

  1. Does this change contain code from or inspired by another project? no

    • Did you notify the maintainers and provide attribution?
  2. Is this a breaking change? no

This changes adds a test to traffic to a service with
multiple ports. Additionally, it configures a TargetPort
on the service that is different from the Port value
exposed to clients to verify OSM currently maps a port
to it's target port while filtering and routing traffic.

Co-authored-by: Shashank Ram <shashr2204@gmail.com>
Signed-off-by: Shashank Ram <shashr2204@gmail.com>
@shashankram shashankram added the wip Work-in-Progress label Sep 2, 2021
@codecov-commenter
Copy link

codecov-commenter commented Sep 2, 2021

Codecov Report

Merging #4074 (64862cc) into main (2cec5d7) will increase coverage by 0.38%.
The diff coverage is 86.07%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4074      +/-   ##
==========================================
+ Coverage   68.44%   68.83%   +0.38%     
==========================================
  Files         206      205       -1     
  Lines       11673    11317     -356     
==========================================
- Hits         7990     7790     -200     
+ Misses       3633     3475     -158     
- Partials       50       52       +2     
Flag Coverage Δ
unittests 68.83% <86.07%> (+0.38%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
pkg/catalog/mock_catalog_generated.go 0.00% <0.00%> (ø)
pkg/envoy/lds/listener.go 89.16% <ø> (ø)
pkg/envoy/types.go 33.33% <ø> (ø)
pkg/envoy/xdsutil.go 88.46% <ø> (-0.22%) ⬇️
pkg/k8s/mock_controller_generated.go 0.00% <0.00%> (ø)
pkg/k8s/types.go 100.00% <ø> (ø)
pkg/providers/kube/fake.go 0.00% <ø> (ø)
pkg/service/mock_service_provider_generated.go 0.00% <ø> (ø)
pkg/envoy/lds/inmesh.go 79.59% <64.28%> (+0.99%) ⬆️
pkg/envoy/eds/response.go 49.20% <76.92%> (+6.34%) ⬆️
... and 17 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 2cec5d7...64862cc. Read the comment docs.

@shashankram shashankram force-pushed the cluster-port branch 3 times, most recently from de37cad to 16356ba Compare September 3, 2021 17:23
@shashankram shashankram changed the title [do not review]: testing pkg/*: address control plane implementation gaps Sep 3, 2021
@shashankram shashankram removed the wip Work-in-Progress label Sep 3, 2021
@shashankram shashankram marked this pull request as ready for review September 3, 2021 17:53
@shashankram shashankram requested a review from a team as a code owner September 3, 2021 17:53
@shashankram shashankram added the release/v1 Release v1.0.0 label Sep 3, 2021
Comment on lines +83 to +87
// GetOutboundMeshTrafficPolicy returns the outbound mesh traffic policy for the given downstream identity
GetOutboundMeshTrafficPolicy(identity.ServiceIdentity) *trafficpolicy.OutboundMeshTrafficPolicy

// GetInboundMeshTrafficPolicy returns the inbound mesh traffic policy for the given upstream identity and services
GetInboundMeshTrafficPolicy(identity.ServiceIdentity, []service.MeshService) *trafficpolicy.InboundMeshTrafficPolicy
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fantastic!

@@ -30,22 +30,22 @@ func TestNewClusterLoadAssignment(t *testing.T) {

cla := newClusterLoadAssignment(namespacedServices[0], allServiceEndpoints[namespacedServices[0]])
assert.NotNil(cla)
assert.Equal(cla.ClusterName, "osm/bookstore-1")
assert.Equal(cla.ClusterName, "ns1/bookstore-1|80")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So glad to see this!

Copy link
Contributor

@draychev draychev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚢 it!

pkg/envoy/eds/response.go Outdated Show resolved Hide resolved
pkg/envoy/lds/response_test.go Outdated Show resolved Hide resolved

// GetEndpoints returns the endpoints for a given service, if found
GetEndpoints(svc service.MeshService) (*corev1.Endpoints, error)
GetEndpoints(service.MeshService) (*corev1.Endpoints, error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the cleanup!

@shashankram shashankram force-pushed the cluster-port branch 3 times, most recently from 0e528c2 to d05f80d Compare September 3, 2021 19:32
permissiveMode := mc.configurator.IsPermissiveTrafficPolicyMode()
if !permissiveMode {
// Pre-computing the list of TrafficTarget optimizes to avoid repeated
// cache lookups for each upstream service.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

pkg/catalog/outbound_traffic_policies.go Outdated Show resolved Hide resolved
pkg/catalog/outbound_traffic_policies.go Outdated Show resolved Hide resolved
remoteCluster.LbPolicy = xds_cluster.Cluster_ROUND_ROBIN
}
// Configure service discovery based on traffic policies
remoteCluster.ClusterDiscoveryType = &xds_cluster.Cluster_Type{Type: xds_cluster.Cluster_EDS}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

pkg/envoy/rds/response.go Outdated Show resolved Hide resolved
Comment on lines -306 to -315
func GetLocalClusterNameForService(proxyService service.MeshService) string {
return GetLocalClusterNameForServiceCluster(proxyService.String())
}

// GetLocalClusterNameForServiceCluster returns the name of the local cluster for the given service cluster.
// The local cluster refers to the cluster corresponding to the service the proxy is fronting, accessible over localhost by the proxy.
func GetLocalClusterNameForServiceCluster(clusterName string) string {
return fmt.Sprintf("%s%s", clusterName, localClusterSuffix)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for cleaning up

Comment on lines +32 to +45

// Port is the port number that clients use to access the service.
// This can be different than MeshService.TargetPort which represents the actual port number
// the application is accepting connections on.
// Port maps to ServicePort.Port in k8s: https://pkg.go.dev/k8s.io/api/core/v1#ServicePort
Port uint16

// TargetPort is the port number on which an application accept traffic directed to this MeshService
// This can be different than MeshService.Port in k8s.
// TargetPort maps to ServicePort.TargetPort in k8s: https://pkg.go.dev/k8s.io/api/core/v1#ServicePort
TargetPort uint16

// Protocol is the protocol served by the service's port
Protocol string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, it prevents all the back and forth code of converting MeshService to k8s service in the code

This change refactors the core control plane components
to address several gaps in the existing implementation.

At a high level, it resolves the following:

1. Traffic to a service with multiple ports is filtered and
   routed correctly based on the port the traffic is directed
   to (openservicemesh#3777). This was previously a blocker for users with
   multiple ports/protocols per k8s service object.

2. Builds mesh traffic policies for LDS/CDS/RDS configs
   inline consistently. This simplifies the code and makes
   it a lot easier to test config generation based on the
   state of the mesh (service, service accounts, SMI policies
   etc.). It also eliminates outbound policy merges that
   are unnecessary. Building config for different envoy configs
   inline will help when OSM moves to an event-driven config
   generation per proxy/group model, where an event can produce
   configuration using a nearly consistent view of the caches.

3. Allows the TargetPort for a service to be different than it's
   Port value. This is common in k8s to allow app authors to
   change the backend port without needing to update their client
   code.

4. Similar to egress, mesh RDS configs are isolated per port to
   avoid conflicts when the same host needs to be routed differently
   per port. This can happen when the same service exposes multiple
   HTTP ports that need to be routed differently.

5. Correctly generates hostnames for a service-port combination that
   clients can use to access the service.

7. Addresses bugs in endpoint discovery when the service has multiple
   ports. Correctly translates a cluster name to a MeshService to be
   able to retrieve its endpoints.

8. Implements SMI TrafficSplit for permissive mode for both HTTP and
   TCP traffic.

9. Removes and simplifies code that are not required, such as the APIs
   to retrieve port:protocol mappings for a service, and other redundant
   APIs.

10. E2e tests and unit tests for various scenarios: multiple ports per
    service, permissive mode traffic split, different TargetPort for
    service Port etc.

To understand this change better, it's important to note the following
fundamental changes made to address the existing implementation issues
noted above.

- A MeshService now carries information on the port, target port and
  protocol associated with the service. The implementation ensures
  the target port is always set for a service with an endpoint. It is
  necessary to disambiguate between the two because k8s allows routing
  traffic to a service whose Port and TargetPort are different. This
  is of significant importance when it comes to filtering and routing
  traffic on the client and server side. A k8s service with multiple
  ports is transposed to multiple MeshService representations within
  the OSM control plane. A MeshService is the source of truth while
  creating filtering and routing configs in Envoy as opposed to a k8s
  service. In other words, filter chains, clusters per service have
  a 1-1 mapping to a MeshService instance.

- RDS mesh inbound and outbound route configurations are built per
  port, similar to egress route configurations. This ensures there
  can be no conflicts when a service/FQDN needs to be routed differently
  based on the port.

- Endpoints for a MeshService is filtered by its TargetPort is set. This
  is required to endpoints not associated with the MeshService are not
  programmed for upstream clusters. This is required to support multiple
  ports per service.

Part of openservicemesh#4052
Resolves openservicemesh#3777
Resolves openservicemesh#2527

Signed-off-by: Shashank Ram <shashr2204@gmail.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
release/v1 Release v1.0.0
Projects
None yet
5 participants