Pattern: sidecars, ambassadors and adapters containers
Containers and architectural patterns: If you have developed distributed solutions, composed of multiple microservices, you will surely have used one of these approaches for solving “common” problems. If like me, you didn’t know that the pattern you chose for your implementation already had a name, here’s some theory for naming solutions that you have already verified to be “common sense” thanks to your experience. And maybe some more ideas for the future. Let’s see what the “sidecar”, “ambassadors” and “adapters” containers are.
Your microservice is a container, which implements part of the business logic of your application. You will immediately realize that, in a distributed solution made up of many microservices, each with its own business purpose, there are a series of common functions independent of the business logic: logging, configuration, security are just some transversal aspects. Patterns are known as “sidecar”, “ambassador” or “adapter” arise precisely from the need to implement these functions outside the business container and to be reused where necessary throughout the entire solution.
Let’s analyze a practical example immediately: our container implements a simple web service, capable of responding to HTTP requests from clients. Suppose we need to evolve our solution by adding a security layer, thus implementing the HTTPS protocol to serve clients. Faced with this new requirement, we have two possible approaches: to directly change the code of the original container or to use a “sidecar” container.
As with motorcycles, the sidecar is placed next to the bike itself, the sidecar container is also positioned next to the legacy container, in order to extend its functionality.
A group of containers is created (a POD) which serves client requests via HTTPS protocol to which the sidecar service responds, which rotates requests (this time in HTTP) to the original container.
The advantages of this approach are many: first, no intervention is required on the original container. Also the sidecar container:
- Implements only the additional security layer and is independent of the business logic
- Can be reused where needed
- Manage SSL certificates
A similar approach can be used in other situations. For example, suppose we want to centrally manage all the configuration parameters of our solution, using a specific service. The service will likely expose information through APIs that the various microservices can query to get the information they need. To introduce this new management, the use of sidecar containers can be very suitable. Let’s think for example of legacy services that plan to read their configuration from the file system (for example from a JSON file). Similarly to what we saw in the previous example, by sharing the file system between the two containers, it is possible that the sidecar microservice takes care of requesting the most recent version of the system configuration via API, and then storing it on the file system and making it available to the container legacy.
An “ambassador” container comes in handy when access to a service, essential for our legacy container, needs to be redirected according to new policies.
Let’s take for example access to a storage area that, growing in size, must be fragmented into more subsystems. In this case, in order not to intervene on the main container and having to implement the same new access logic on all impacted services, an ambassador container is created that mediates access to the storage area. The routing logic towards the individual subsystems is then implemented on this new container and replicated where needed.
One of the most interesting applications of an ambassador-type container is in the experimental field: let’s imagine we want to test the deployment of a new version of our microservice, hijacking only a small percentage of the total requests. This deployment mode, called Canary, can be managed with a container ambassador who rotates the desired percentage of traffic to the new container.
The last type of container that we are going to deal with in this post is “adapter”: in this case, we need to adopt an exposed interface of our legacy container so that it becomes usable by an external service on which we have the opportunity to intervene.
An example of using adapter containers is the introduction of a legacy container monitoring layer, to collect, for example, metrics collected by applications such as Prometheus.
Let’s consider a simple web application created in Flask and Cached Redis. The solution is built with two containers and a volume dedicated to hosting the HTML resources to be published.
We introduce a sidecar container that takes care of dynamizing the updating of HTML resources, periodically monitoring a Git repository and executing a PULL operation to update the local copy of the resources.
Let’s see the related docker-compose file.
The sources of the entire solution are available in this repository.
Whether it is to extend the functionality of one of our microservices, to mediate access to a service on which it depends, or to adapt its response to an external service, the most suitable solution is to introduce a dedicated, business logic independent and reusable container. What other applications come to mind? A great book to learn more about is Designing Distributed Systems, by Brendan Burns.
Did we have fun? See you next time!