Understanding Service MeshLearn more about cloud-native apps
Learn more about cloud-native apps
A cloud-native app has been designed and written specifically to run in the cloud and take advantage of the properties that this type of infrastructure has. An organisation can consider itself “cloud-native” when it has also adopted supporting DevOps workflows and practices in order to enable greater agility, increased speed, and reduced issues for both the app and the organization itself.
In common cloud-native app architectures, each cloud-native app is composed of a number of loosely-coupled and highly-cohesive microservices working together to form a distributed system. Loosely-coupled means that an individual microservice can be changed internally with minimal impact on any other microservices. Highly-cohesive microservices are built around a well-defined business context, and typically any modifications required are focused around a single area of responsibility or functionality.
Cloud-native applications are often packaged and run in containers. The underlying cloud infrastructure often runs on shared commodity hardware that is regularly changing, restarting, or failing. This means that a microservice should be designed to be ephemeral in nature. It should start up quickly, locate its dependent network services rapidly, and fail fast.
Dividing an app into numerous microservices increases its flexibility and scalability, but the potential downside is a reduction in availability due to the app’s increased complexity and more dynamic nature. There are also concerns about the app’s security and observability--being able to monitor its state--when an app is divided into more pieces. You can learn more about ways to address these concerns here.
Now let’s take a closer look at cloud-native apps.
Introduction to Cloud-Native Apps
If an organization wants to become cloud-native, this doesn’t just mean that all of their applications have been designed and written specifically to run in cloud environments. In addition, it also means that the organization has adopted development and operational practices that enable agility for both the app and the organization itself.
The cloud-native app architecture evolved in response to many of the common bottlenecks that slow down the development and release of monolithic applications, namely avoiding code “merge hell” that follows from a large number of engineers working around a single codebase, enabling independent release of individual services, and providing a limited area to investigate -- a blast radius -- when a component does fail. The cloud-native approach enables development and operational staff to rapidly make localized decisions related to their defined responsibilities and carry those decisions out.
DevOps workflows bring together software development and operations in order to enable faster, more frequent software releases that have a lower rate of failure, with the goal of achieving the principle of continuous delivery.
In common cloud-native app architectures, apps are typically written as a number of independent microservices, each one performing a different function. The microservices are deployed and run within container technologies in the cloud. The microservices communicate with each other across the cloud in order to collectively provide the app’s functionality, working together to form a distributed system.
Common Cloud-Native App Architectures
Before cloud-native apps, an app’s architecture was often a single “monolithic” entity that consisted of a single service providing many functions. The cloud-native approach means that instead of being a single service, the app is composed of multiple microservices, each performing a well-defined single function or small set of functions.
The diagram below shows two examples: a single-service app, and a cloud-native app made up of three microservices. The single-service app is depicted by a pie, reflecting that the app is a single entity that must be made as a whole. The cloud-native app is depicted as cupcakes, meaning that each cupcake (microservice) is independent of the others and can have a different recipe (language, etc.).
Each microservice is decoupled from the others, meaning that any microservice can be changed internally without needing to change any other microservices. So you can change the recipe for one cupcake without needing to change the recipes for the others. With a pie, changing the recipe affects the whole pie.
Most microservices are also designed to be ephemeral, meaning that they can safely and rapidly stop and start at an arbitrary time. For example, an underlying virtual machine that a service is running on may restart, which in turn triggers a restart of all the processes and applications running on that VM.
In the cloud-native app, two of the microservices communicate with the third microservice. The single-service app and the cloud-native app can provide the same functionality.
The diagram below adds a third example: an app with ten microservices, each independent of the others, depicted as ten different types of cupcakes. This hints at how much more complex a cloud-native app’s architecture could be. For example, some of the microservices are communicating with more than one other microservice. All 10 microservices are part of a single distributed system. Each of the 10 microservices can be anywhere, as long as it can communicate with the others it needs to.
Cloud-Native App Benefits
From a developer viewpoint, the primary benefit of cloud-native apps over other apps is that each part of the cloud-native app can be developed and deployed separately from the others. This enables faster development and release of apps. Also, returning to the pie versus cupcake analogy, in cloud-native apps each microservice can be customized and tailored to meet specific needs. Customizing one cupcake doesn’t affect the others.
To illustrate benefits for developers, let’s look at two scenarios.
Scenario 1: Suppose that a cloud-native app consists of 10 microservices, with each microservice developed by a different team and loosely coupled with the other microservices. If one of those teams wants to add a new feature, it revises its microservice, and then the updated microservice can be shipped immediately, thus achieving continuous delivery.
The same is true for all teams in this scenario, so no team will be delayed because they have to wait for another team working on something unrelated.
Scenario 2: Suppose that a single-service app provides the same functionality as the cloud-native app from Scenario 1. Ten teams develop parts of the single-service app. If one of those teams wants to add a new feature, it has to wait until the next scheduled release of the app, which is dependent on the other teams completing their current development work whether or not their work is related to the first team’s.
For the first team, there could be a delay of days, weeks, or months before their new feature finally ships. Delays from one team will affect all other teams--one team falling behind schedule could hold up all others from releasing their new features.
So cloud-native apps allow organizations to be more agile and innovative, taking advantage of new opportunities immediately.
From an operations viewpoint, the primary benefit of cloud-native apps is that each microservice comprising the cloud-native app can be easily deployed, upgraded, and replaced without adversely affecting the other parts of the app. Each microservice should be independently releasable and deployable.
Additional copies of each microservice can be deployed or retired as needed to scale the app up or down in order to meet demand. New versions of a microservice can be rolled out and users transitioned to them without having an entire app outage. Copies of the same microservice can also be running in multiple clouds.
If developers follow established cloud-native best practices, such as designing applications to support Heroku’s Twelve Factor App principles, this further increases the operability of applications. Examples include treating backing services (database, messaging, caching, etc.) as attached resources, executing the application as one or more stateless processes, and maximizing robustness with fast startup and graceful shutdown.
The Fallacies of Distributed Computing
As we mentioned earlier, cloud-native applications are typically decomposed into individual microservices. This decomposition of business logic results in a distributed system, as the microservices need to communicate with each other over a network in order for the application to function.
Decades ago, Peter Deutsch and his colleagues at Sun Microsystems created a list of the fallacies of distributed computing. (https://web.archive.org/web/20160909234753/https://blogs.oracle.com/jag/resource/Fallacies.html) It pointed out that people often make incorrect assumptions when they transition from non-distributed to distributed models.
Cloud-native apps--and the cloud, for that matter--didn’t exist when that list of fallacies was compiled. Several of those fallacies are potentially relevant to cloud-native app adoption today, including the following:
- “The network is reliable.
- Latency is zero.
- Bandwidth is infinite.
- The network is secure.”
Things can and will go wrong with any app, but there are more opportunities for failures with distributed apps than non-distributed ones.
Common Challenges for Cloud-Native Apps
Let’s take a closer look at some of the common challenges cloud-native apps can pose.
The first of these challenges is being able to find all the microservices that are dynamically deployed. As microservices are migrated from one place to another and additional instances of microservices are deployed, it becomes increasingly difficult to keep track of where all the instances are currently deployed at any given time. This challenge is commonly referred to as service discovery.
A second challenge involves security and observability. Cloud-native apps are inherently more complex. This makes them harder to secure, because they have larger attack surfaces and more logical pieces to protect. This also makes it harder to monitor how the app is operating and understand what’s happening when things go wrong, especially for app debugging purposes.
With an application built using a monolithic architecture, a crash in one component typically crashed the entire application. Using a microservice-based architecture enables a crash in one service to be isolated, thus limiting the blast radius and preventing the entire application from crashing. However, it is quite possible that a failure in one service can cascade throughout the entire application if this is not handled properly. The more services there are, the more chance of a partial failure turning into a system failure, which decreases availability and increases the application’s downtime.
Take another look at the diagram we examined earlier, and think about it in the context of each of these challenges.