Privileges escalation in kubernetes

Microservices for the win

Written on Wed Mar 01 2023
11 minutes reading
microservice
architecture
DevOps

Introduction

During my studies as a DevOps engineer, I had the chance to familiarize myself with many concepts and challenges around the web, from the architecture of an application to the management of its infrastructure through its development. I had the opportunity to participate in the migration of a monolith to a microservice architecture. Today, I want to share with you what I learned from this experience and what I retain from this development approach. In this article, I will present the advantages and disadvantages of a microservices architecture, as well as the main challenges to overcome when implementing this architecture.

Architecture what?

A microservices architecture is an approach to software application design that consists of breaking the application into a series of independent services that communicate with each other via clearly defined interfaces. Each service is designed to perform a specific function and can be developed, tested and deployed independently. The goal of this approach is to make applications more scalable and flexible, by allowing each service to be updated or modified independently without affecting the entire application. It can also make development and deployment more efficient, by allowing different services to be developed and deployed independently. This architecture can be an interesting solution for projects that require high scalability and flexibility, but there are also additional constraints in terms of complexity and maintenance.

It's not a quick fix

I prefer to start by introducing the microservices architecture as not being your solution, this is a choice on my part. We often hear that microservices would solve this or that...

Keep in mind that even roses have thorns.

It is not necessarily wise to move to a microservices architecture for several reasons. First of all, it is undeniable that you need to have a large enough and competent development team to set up and manage this architecture. This can be costly, time-consuming (and it will take much longer than estimated). In addition, a microservice architecture can be complex and difficult to debug, as it involves many independent services that must be properly integrated and tested. Choosing to do a microservices architecture implies that you have to change the infrastructure, not just change your hosting solution. You have to revisit the way you structure your application, it also implies to set up patterns to allow integration of your microservices, define the communication flow of your microservices, permissions between them. Although adopting a microservice architecture is not simple, it is not inaccessible. That's why I'll try to give you the keys that helped me during this challenge, and share the experience I got from it.

Road to microservices

I would not recommend starting the development of your application on a microservices architecture, it would lead to difficulties and challenges that do not make sense to be encountered from the beginning of an application. Microservices should, in my opinion, be seen as an extension of your application, a goal. While it is important to understand that the decision to split an application into microservices depends on many factors, such as the size of the application, the complexity of its architecture, the available resources and the company's goals. There is no single answer to the question of how to split an application into microservices. It is all the more influential to make good technical choices at the beginning of your application development, if possible, to allow for timely architecture migration. By good choice, I mean using a technical stack that is flexible, maintained with an active community, aligned with today's web considerations. (modularity, asynchronous processing, responsiveness, etc.) Starting to break down your code by business, even in a monolith can help :)

Make your services independent

I will develop my article by considering a split of the microservices by domain of the application. However, note that it can be very interesting to split your services to group them not by domain, but by business functionality or improvement priority.

Understand the responsibilities of its application

The first temptation you might have, and which will quickly turn out to be a very costly maintenance mistake, would be to make a microservice per database element/entity of your application. Indeed, this choice ensures the independence of each element, but it is perhaps too independent. In general, we will split a user service allowing to manage everything concerning authentication (if not external to the application), authorizations (if necessary) and stored information concerning your users.

Splitting your architecture in domains

Database management

Databases are known to be a Single Point Of Failure of applications, because they are often heavily used by many actors. Consider it mandatory to have one database per microservice, many advantages emerge such as ensuring data confidentiality and availability, but also ensuring inter-service isolation. And obviously, allow using several database paradigms!

Architecture schema including databases

A golden rule for avoiding long-term debt is not to duplicate data. Remember the user service that we cut out earlier? Don't even imagine that we are going to duplicate the user database in each service that has relationships with the user entity. You will have understood that it will be necessary to rethink the relationships between your entities... Well, not really, some relationships will become inter-service dependencies and others will be retained.

Breaking down services by business area

It would be utopian to think that all your services will be independent. I think that you should not try to have no dependencies, but to have a minimum and at lesser cost. I learned the hard way that you shouldn't make a microservice for something that can't live alone. Let's take for example a simple e-commerce application, we have products and users can put comments and notes to a product. Making a microservice "product metadata" would be a mistake, because this service could not exist without the product service, so we have no interest in making it a separate service, and it will be much more comfortable to keep it in the product service. Breaking down the microservices into business domains consists of identifying the different businesses of your application and creating domains composed of one or more services that are associated with this business. This means that each microservice focuses on a single business functionality and is responsible for implementing that functionality autonomously.

Architecture schema definning interactions

To identify your organization's business areas, you must first understand your company's functionality and responsibilities. This can be done by analyzing business processes, using your organization's data, conducting employee interviews or analyzing your product or service line. The goal is to determine how to group these functionalities and responsibilities so that they are clearly defined and isolated from each other. Note that slicing up microservices by business domains can lead to intrinsic dependencies between the different domains, so it is important to consider these dependencies when making the slicing decision. In addition, it is also significant to find the right balance between granularity and functionality so as not to create overly granular microservices that can be difficult to maintain.

Inter-service communication

Choosing how your services will communicate is not an easy task and you will definitely hesitate for a long time on your choices because of the fear of making a mistake.

The better is the enemy of the good.

I believe that it is normal to make mistakes, and fortunately for you, it will not be impossible to rework the communication method later on. Let's say I hesitate between 2 communication methods, I would choose the easiest one to implement, even if I have to change it later because the application will have more traffic or new features, but it's useless to add difficulty now, we are already changing the architecture!

What solutions?

For inter-service communication, we have 4 main areas of research:

  1. HTTP: Using HTTP for communication between services is a simple and common solution. Services can send HTTP requests and receive HTTP responses, making it easy to build and consume these services. However, this solution may be less efficient in terms of latency and reliability than other options.
  2. Messaging: Using messaging for communication between services allows these services to be decoupled from each other. This means that services do not need to know the location or availability of the other services they are communicating with. However, this solution is more complex to implement and manage than other options. (e.g. Apache Kafka)
  3. Remote Procedure Calls (RPC): Remote Procedure Calls (RPC) allow a service to call a function in another service transparently, as if it were a local function. This solution is simple to use, but may be less efficient in terms of latency and reliability than other options. (e.g. gRPC)
  4. Streaming: streaming data between services can be an effective solution for use cases where there is a large volume of data to process. However, this solution can be more complex to implement and manage than other options. (e.g. Apache Flink)

How do you choose?

You will need to make a choice highly dependent on your cut and the type of interactions between the business areas of your application, furthermore, you could decide to use several types of communication depending on your needs, this is quite common.

There is no best solution, there are just some solutions that are more suitable for the needs than others.

From my perspective, using HTTP or RPC to get your organization's microservices to communicate may be a wise option at first, as these communication protocols are widely used and well known, making them relatively simple to implement. In addition, they are also relatively easy to evolve over time, which can be useful if you need to change the way your microservices communicate. If you choose to start simple, but are unsure which of these two protocols to go with, I think you'll want to choose based on how your services are expected to interact, but also their accountability. RPC will be preferred if your service retains responsibility for an action, but requires a specific function to be called in the other service, otherwise, if my service needs to send an instruction or request to the other service while delegating processing responsibility to it, then HTTP seems to be more suitable.

Designs pattern to the rescue!

Design patterns are generic and reusable solutions to design problems that frequently arise in software development. They are used to document the know-how and best practice of the software development community, so that other developers can solve similar problems quickly and efficiently. Design patterns are generally classified into different categories, such as creation, behavior and structure design patterns. They are described using a common language and a standard format, which makes them easily accessible and understandable for developers.

I personally learned a lot about this topic from refactoring.guru, I recommend you to have a look at it!

The use of these patterns will depend a lot on how the business domains and the interactions they had between them were separated. However, some patterns are often present in a microservice architecture, for example, if we come back to the concern of our user service which is solicited by all the other services, we can often solve the interactions by implementing an aggregator. An aggregator is a microservice whose role is to group together data from different microservices and present them in a single form. This can be useful in a microservice architecture when multiple services need to access data from a specific service, which is frequently seen with the user service. By using an aggregator, you can reduce the dependency of other services on that specific service, improve problem isolation, and improve the scalability of your architecture by spreading the load over multiple instances.

Architecture schema including aggregator pattern

This diagram is purely for illustrative purposes and is in no way a solution, however what may be interesting to note is that the aggregator is a microservice in itself and this makes sense given what we said earlier because it fulfills a specific functionality which is to aggregate data. It should be noted that it could be possible to do the aggregation in the gateway, however I would not recommend it, because from my point of view, this is not the role of the gateway.

Ensure security across microservices

We will only talk here about security in terms of design and infrastructure, because the rest is very dependent on the implementation you will perform.

Prerequisites

Obviously, it will be necessary to encrypt inter-service communications using security protocols such as TLS. Also, consider ensuring the security of each microservice individually, by implementing security measures such as input validation and code injection protection. Remember that a service is responsible for its resource, and should verify the input it receives.

Zero trust model

A zero trust model is an approach to security that assumes that all users and resources in a given network are potentially dangerous and must be verified before being allowed access to protected resources. The zero trust model is in contrast to the traditional approach to security, where users and resources within a defined security perimeter (such as a LAN) are trusted by default. The zero trust model can be particularly useful in a microservices architecture, which consists of decomposing an application into a series of small independent services that communicate with each other to provide more complex functionality. In a microservices architecture, each service can be considered as a resource to be protected, and the zero trust model can be used to ensure that only authorized services have access to the resources they need.

Don't trust your services

In the world of distributed systems, it is important to understand that services cannot trust each other, indeed, this goes against the very nature of distribution. In a distributed system, services are designed to operate independently of each other, each with its own functionality and responsibility. This means that they cannot rely on the integrity of other services, because they cannot be sure of their state or behavior. In addition, distributed systems are subject to changing conditions, such as configuration errors, hardware failures and network disruptions. These conditions can lead to errors or inconsistencies in the data or commands exchanged between services.

A tool like Consul by Hashicorp makes sense to use, as it provides service discovery and service configuration capabilities that can help solve the challenges of managing microservices in a distributed environment. Service discovery allows services to reliably locate and communicate with each other, while service configuration allows configuration settings to be managed in a centralized manner.

Note that Consul implementation will be complex and should only be addressed when your application has found its flow and your interactions between services are clearly defined.

The importance of a trustworthy source

You will surely think about implementing a very high security measure within your application. There are very advanced concepts and solutions to ensure a high level of security within your infrastructure and application.

A key to security is how you manage your secrets, from your [JWT] encryption key (https://jwt.io) to database credentials. It's important to design how you're going to distribute your secrets, at the application level first, but at the infrastructure level later. This is where projects like Vault by Hashicorp come in, allowing you to create an entity designated as a trusted source and restricting access to improve security. Vault offers many interesting services such as database credential rotation, certificate creation like a PKI (Public Key Infrastructure), data encryption and more advanced secret management.

Note that implementing a Vault will be complex and should only be addressed when your application has found its flow and your interactions between services are clearly defined.

Conclusion

Implementing a microservice architecture is often a difficult and costly ordeal. Like any challenge, it is important to work upstream and anticipate potential future problems that will arise. Before starting any process, take the time to establish the elements, how you would split your services, why you want to move to a microservice architecture. Also, take the tension with your team, is everyone up to speed, is your communication between your teams adequate or do you maybe need to set up a DevOps position to organize the teams? I hope that my feedback has given you a clearer idea of what microservices are, and what you should expect if you choose to implement such an architecture.