A true microservice approach moves far beyond the design of web services interfaces. The development of reusable and independent software components is possible. Container-based architectures give us more power: the power of Pods.
When thinking about microservices, we often read about splitting monolithic backend API into a set of smaller web services. This should ensure a lot of cool stuff such as team scalability, independent deployments, increased reliability through isolation. Actually, more than one piece of code can be designed to achieve these properties.
In this post, I'll discuss some classic design pattern and use cases, that I wish I had known before. These classic design strategies can be found, sooner or later, when dealing with containers and their orchestration on Kubernetes.
What's a Pod?
A Pod is the smallest unit of deployment in Kubernetes. It represents a set of application containers that run in a shared context and as such, they can access the same network interface, as well as can mount shared volumes. Containers in the same Pod will land on the same machine, while containers on different Pods may run on different servers.
One of the best advantages of a microservice approach is the isolation from the underlying technology. The Pod abstraction allows designing pieces of software that are completely independent of the particular technology or framework. These containers can then be reused by different components in the architecture communicating via file system or network interface.
As result, it comes out that understanding how to use Pods, gives a powerful tool to develop architectural cross-cutting concerns, simplifying application development and integration. Next, I'll describe some classic patterns that help in:
- providing support features to the main application container
- hiding external world complexity
- providing access to internals through a standardized interface
A Sidecar or sidekick container allows to transparently extend the features of the main container. Some common use cases are log collection, external content synchronizer as well as proxying requests. In general, it provides a solution to all those concerns we do not care about in development but are critical in production.
The sidecar pattern provides both architectural and operational advantages. It can help in reducing the cost of integration and letting the efforts to be focused on the real business logic. External services can be reached via localhost and complex integration with language-specific libraries can be avoided. Moreover, since containers are still unit of isolation and encapsulation, sidekick components can have dedicated resource configuration and can be updated and rolled back independently of the main container. In other words, they provide a failure boundary that let the side process to degrade or even stop while preserving the availability of the main one.
An ambassador service is an abstraction to proxy requests towards an external service. It can be used to hide the outside world complexity by providing a simpler interface to the consumer. The hidden complexity may reside in standards concerns like authentication, monitoring, and logging as well as custom access policies such as retry patterns, circuit breaking, and sharding. Ambassadors can be deployed as sidekick containers to serve the needs of the consumer in a reusable and decoupled way.
Thanks to ambassadors, developers can forget about intricated access policies that must be rebuilt from scratch in each consumer. To access a service an application can use its localhost network interface as if the service was on the local machine. The Pod abstraction ensures the latency is minimized by colocating the sidecar container on the same host of the consumer.
An adapter is the inverse pattern of an ambassador: while the latter simplifies access to the external world, the former offers a unified interface to access the pod internals. It does this by standardizing output and communication protocols, thus the application can respond to the external world without being modified. As result, it is a powerful design tool to control and interact with heterogeneous applications. For example, with the adapter pattern, one can design a unified monitoring interface as well as pieces of a distributed middleware that require being modular and reusable.
I hope you found this topic interesting as I do. To know more check out this couple of links:
Thanks for reading 🤓