Consul Service Mesh on Kubernetes Design Patterns
Consul service mesh provides a robust set of features that enable you to migrate from monolith to microservices in a safe, predictable, and controlled manner.
This tutorial focuses on explaining the Consul service mesh architecture from the perspective of the design patterns it implements. Design patterns are not to be considered an end unto themselves, but rather by mapping system elements to the pattern language, developers can more quickly understand the purpose of a component or subsystem. This tutorial is designed for practitioners who will be responsible for developing microservices that will be deployed to Consul service mesh running on Kubernetes. However, the concepts discussed should be useful for practitioners developing microservices that will be deployed to any environment.
This tutorial will:
- Describe the logical components of a service mesh
- Explain how Consul acts as a facade, or abstraction layer, over lower level network primitives
- Explain how Consul manages services without dependencies, using to the sidecar pattern.
- Explain how Consul implements the Ambassador pattern to power the service mesh
- Explain how Consul implements the Adapter pattern to enable service sync between Consul and Kubernetes
If you are already familiar with the internals of Consul, or are more interested in understanding how to plan a microservices pilot project, feel free to skip ahead to the next tutorial in the collection which covers how to Model the Monolith as a set of microservices, or if you are already familiar with modeling microservices skip to Scope a Microservice Extraction, which will discuss more Consul service mesh specific concerns.
The tutorials in this collection reference an example application. You can review the source code for the different tiers of the application on github.
Components of the service mesh
From the microservice developer's perspective, the Consul service mesh can be thought of as a runtime environment that allows you to significantly reduce network complexity, service discovery, configuration management, and some aspects of application security to an external and configurable network management layer. Some of the advantages of this approach include less application code dependencies, the ability to focus service logic on the core business domain, and the ability to mutate service behavior without redeploying.
The logical components of a service mesh are the control plane, and the data plane. Control plane components of the Consul service mesh are implemented as agents that run on each node in the mesh. An agent operates as either a server or a client.
The control plane is the central hub of activity for the service mesh. The control plane consists of all the server and client agents in the service mesh. It maintains state and is the ultimate source of truth. All service mesh configuration and security policy resides in the control plane, and the control plane is responsible for enforcing policies about how traffic is allowed to flow throughout the mesh. The control plane is also responsible for ensuring unhealthy services are removed from the mesh. The control plane provides this functionality by maintaining the current state of the service mesh in terms of health, availability, and location, and then applying the current mesh configuration to that state, to produce a final snapshot of what is routable and what is not, and by whom. It then replicates this information to the data plane.
The data plane is the delivery system of the service mesh. The data plane is made up of all the envoy proxies deployed to the mesh. It receives all the current service mesh configuration (service registrations, intentions, config entries, etc.) from the control plane, and then uses that information to forward requests from services within the mesh to the correct location. It also forwards information about the availability, health, and of services on each node back to the control plane.
Design patterns in Consul
Consul implements many design patterns in its architecture. Describing Consul using the pattern language can be a useful way to orient yourself quickly to all different components of the mesh, how they work, and what service they provide to the Consul environment.
The facade pattern is used to encapsulate the internals of a complex subsystem or library. A common facade that you may have encountered as developer is a database library that provides a more developer focused interface than a low level database driver like ODBC, JDBC, or database/sql. A database driver is used to adapt an underlying datastore, such as MySQL or Postgres, to common intermediary abstraction. In most language ecosystems, additional libraries wrap the connection oriented database driver layer with another layer that exists to handle more database consumer oriented activities like transactions. A good example of this is the Microsoft EntityFramework DatabaseFacade.
Consul uses Envoy as a proxy in its data plane. The relationship between Consul and Envoy is similar to the previous example. Envoy provides low level network proxy features, but is highly complex. Service mesh in general, and Consul specifically, is an abstraction on top of networking primitives like proxies that provides a single, simplified conceptual model for consuming underlying network primitives like proxies or load balancers.
The sidecar pattern is used to enhance or augment an application or service. With the sidecar pattern, an additional container, called a sidecar, is deployed to each Pod alongside the application container(s) to provide some sort of capability that the application code doesn't necessarily need to be aware of. Consul uses the sidecar pattern to inject an Envoy proxy that acts as the data plane into each service pod.
This section refers to the Ambassador design pattern, not the Ambassador API Gateway.
The ambassador pattern is a specialized form of sidecar, and is used to broker network traffic to and from a container or process. When implementing the ambassador pattern on Kubernetes, you deploy a Pod that has an application container where the service you wrote runs, and Consul ensures an additional container is deployed that proxies all external network communication. This additional container acts as the ambassador for network communications. The benefit of this approach is that you can focus your application code on logic relevant to your core business domain, while offloading complexities like certificate negotiation to the ambassador container.
Consul leverages the ambassador pattern to handle most of the service mesh concerns that your application code doesn't really need to be aware of. Since all containers within a pod share networking and communicate over localhost via ports, Consul is able to inject an ambassador container into your service pod that handles:
- Data plane configuration
- Consul intentions
- Service discovery
- L7 traffic management such as routing, splitting and resolution
The adapter pattern is used to allow one component or service to be consumed by another by wrapping it in an interface that the consumer knows how to use. This explanation is so common that the pattern is often also referred to as the wrapper pattern. Adapters in daily life may come in the form of something like a headphone jack adapter for your phone, but software uses adapters too.
One way that Consul service mesh implements the adapter pattern is with its service sync feature. By registering Kubernetes services with Consul, or vice versa, you are effectively providing an interface that the consumer expects and can use.
Now that we have established a common pattern language that can help you conceptualize Consul, we will move on to the actual process of implementing our first microservice. In the Model a monolith as a set of microservices tutorial, you will work through the process of modeling a monolith as a set of microservices and planning a microservices pilot project.