The Dark Side of Microservices
Microservices, especially containerized microservices, are very much in fashion in today’s modern software architectures. Rather than developing large, monolithic solutions, the microservices approach allows us to design and implement very specific, narrowly-focused, components that are “wired up” with other components to solve complex problems. At Level 11, we already implement solutions with microservices that enable us to take our functional designs and translate them directly into discrete software components. By using a containerization technology such as Docker, we can furthermore implement these components in a way that isolates them from deployment-time details. A dockerized microservice works just as well under RedHat 7 as it does under Ubuntu 18.04 and doesn’t require any messy OS configuration to deal with software dependencies. At deployment time, we can use a container orchestrator such as Kubernetes, Swarm or Mesos/Marathon to run our microservices and deal with operational issues such as network routing, security, load-balancing, high-availability, logging, monitoring, and deployment.
It sounds wonderful, and it is, for the most part! Those of you who have built large microservices-based systems have probably discovered a few things that are not so wonderful; they’re not quite miserable but they’re certainly more complicated. Here are five areas of microservices to focus on when adopting this design approach:
- Service discovery
- Overlay networking
- Load-balancing and high availability
- HTTPS/TLS Termination
- Maintenance and support
Before microservices, it was typical to run software components on their own computers (virtual or physical). Back-end components were typically accessed using HTTP REST or SOAP on a standard port. If multiple components were run on the same computer, these components were assigned different ports (e.g. one service might run on port 8080 and another on 8081). Connecting services to other services through a shared network was pretty straightforward; a configuration file would be provided, identifying the server address (via DNS name or IP address) and the port where it could be reached.
When running containers in a computer cluster, this approach is much more complicated. First, where a service runs is determined by the container orchestrator, and they might deploy several microservices over many nodes. The machine(s) that a service runs on is determined by a variety of factors including machine characteristics (amount of memory, CPU resources) and its current work loads. Additionally, container orchestrators don’t generally assign static network port numbers to microservices.
Typically, network ports are assigned dynamically and, thus, require more complicated mechanisms for “service discovery,” i.e. figuring out how to connect to other services. Some common mechanisms include tools like Consul or Istio, offering managed authentication, encryption, communication between services, automatic tracing, monitoring and logging of all the services connected. The service discovery space is evolving rapidly, making it more challenging to identify the right tool that will meet your changing needs. However, simply including these mechanisms will expand the footprint of your system and should be considered.
Docker provides each container with its own software-defined virtual network. For example, running ten containers on a machine is like having ten more network cards in your system. Each is assigned its own network address, typically, on a distinct overlay subnet from the physical network. This approach is beneficial because it enables the isolation of container network traffic and much finer control over which services get to talk to each other. A service that needs to connect to a database container can do so in a fashion that none of that network traffic is accessible by other services or, more importantly, by a hacker who might gain access to only the “real” network.
The drawback of this mechanism is the resulting additional complexity for service discovery and other network topology.
Load-balancing and High Availability
In the “old days,'' you might run three instances of a service on three VMs, likely all on the same port (e.g. 8080). You’d then go to your hardware load balancer and create a virtual address entry, used by callers of the service. You would map the virtual address to the three physical addresses on the three VMs. The hardware load balancer would then make round-robin calls to the virtual address and would perform health checks on the instances, adjusting its routing if it detected a non-functioning instance.
With orchestrators, load balancing generally doesn’t require an expensive, dedicated hardware device. The orchestrator can handle calls to the virtual service address and route to the multiple instances that it created at deployment. Some orchestrators, for example, Kubernetes, can spin up new instances and automatically adjust its routing behavior.
In the pre-container days, load balancers would frequently serve as termination points for HTTPS/TLS. The load balancer would host the appropriate TLS certificate to satisfy the calling clients. Downstream communication would work over HTTP or could use free, self-signed certificates such as Let’s Encrypt.
With containers, termination has to occur in the orchestrator or in designated edge-proxy services (e.g. Nginx or HAProxy) or service-mesh services (e.g. Istio or Linkerd) which then have to be deployed and managed just like your other services.
Maintenance and Support
Diagnosing problems on a system where your services might be running anywhere is a challenge. Traditionally, you’d connect to a server with a known problem and examine the log files on that system. However, with containers and microservices, even knowing what server to connect to can be a challenge. After connecting to the orchestration system, you might have to go back and look inside the container to find your log files, or you might have to modify your microservice to print all logs to the console because containerized microservices don’t generally write to files outside the container.
A much more sustainable approach is to use a log aggregation solution, such as the Elastic Stack (ELK Stack) Elasticsearch, Logstash, Kibana. Together, these log aggregation solutions can gather, index, and report on your log files, collectively.
The benefits provided by microservices and containers do not come for free. The technology provides many benefits but requires a more sophisticated organization to develop, test, deploy and support solutions. Companies pondering the move to this technology should be aware of training requirements and should seek outside help if necessary to make the transition.
In spite of the costs, the benefits to microservices and containers are well worth it, although adopters may take a while to realize this!
Manuel Vellon is CTO and Partner at Level 11. He provides the practice-wide vision for our software engineering efforts while anchoring the team with his over 25 years of leadership and experience building and leading high performing engineering teams.