background-shape
feature-image

Resilient cloud-based applications require features such as circuit breaking, routing, metering and monitoring, and the ability to make network-related configuration updates. It may be difficult or impossible to update legacy applications or existing code libraries to add these features, because the code is no longer maintained or can’t be easily modified by the development team.

Network calls also require substantial configuration for connection, authentication, and authorization. If these calls are used across multiple applications, built using multiple languages and frameworks, the calls must be configured for each of these instances. In addition, network and security functionality may need to be managed by a central team within your organization. With a large code base, it can be risky for that team to update application code they aren’t familiar with.

Solution

Put client frameworks and libraries into an external process that acts as a proxy between your application and external services. Deploy the proxy on the same host environment as your application to allow control over routing, resiliency, security features, and to avoid any host-related access restrictions. You can also use the ambassador pattern to standardize and extend instrumentation. The proxy can monitor performance metrics such as latency or resource usage, and this monitoring happens in the same host environment as the application.

When to use this pattern

Use this pattern when you:

  • Need to build a common set of client connectivity features for multiple languages or frameworks.
  • Need to offload cross-cutting client connectivity concerns to infrastructure developers or other more specialized teams.
  • Need to support cloud or cluster connectivity requirements in a legacy application or an application that is difficult to modify.

This pattern may not be suitable:

  • When network request latency is critical. A proxy will introduce some overhead, although minimal, and in some cases this may affect the application.
  • When client connectivity features are consumed by a single language. In that case, a better option might be a client library that is distributed to the development teams as a package.
  • When connectivity features cannot be generalized and require deeper integration with the client application.

Here is an example of how you might implement the Ambassador pattern in Go using the Kubernetes API:

This code sets up a connection to the Kubernetes API server and creates a Kubernetes client.

It then creates an HTTP handler function that routes requests to the appropriate microservice based on the request parameters and makes a request to the target microservice using the Kubernetes client.

The response from the target microservice is then written back to the client.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

package main

import (
	"fmt"
	"net/http"

	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

func main() {
	// Set up a connection to the Kubernetes API server
	config, err := rest.InClusterConfig()
	if err != nil {
		panic(err.Error())
	}
	// Create a Kubernetes client
	client, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// Route the request to the appropriate microservice
		// based on the URL path or other request parameters
		targetService := routeRequest(r)
		// Make a request to the target microservice
		response, err := client.CoreV1().Services(namespace).ProxyGet(targetService, targetPath, params).Do()
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		// Write the response from the target microservice back to the client
		w.Write(response)
	})

	http.ListenAndServe(":8080", nil)
}

func routeRequest(r *http.Request) string {
	// Determine the target microservice based on the request parameters
	// For example, you might use the URL path or query string to determine the target service
	return targetService
}

Keep in mind that this is just a simplified example and may not include all of the features and functionality that you might want in a real-world ambassador service.

There are a few reasons why you might not want to use the Ambassador pattern in your Kubernetes application:

Complexity:

The Ambassador pattern adds an additional layer of complexity to your application by introducing an extra service that acts as a proxy between the client and the target microservices. This can make it more difficult to understand and maintain the application.

Performance:

The Ambassador pattern can potentially add overhead to your application by requiring an extra network hop for each request. This can impact the performance of your application, particularly for high-traffic applications or applications that require low latency.

Limited functionality:

The Ambassador pattern may not provide all of the features and functionality that you need for your application. For example, if you need to add advanced routing or load balancing capabilities, you may need to use a more feature-rich ingress controller.

In general, the Ambassador pattern is most suitable for relatively simple applications that do not require advanced routing or load balancing capabilities. If your application has more complex requirements, you may want to consider using a different approach, such as an ingress controller or a service mesh.