Introduction
Istio feels like yet another step in a long chain of evolution of software systems. What comes to mind is perhaps a variation on the famous quote “standing on the shoulders of giants:” “building on the shoulders of giants.” Istio builds on Kubernetes, which in turn built on containerization technology, and the chain continues back in time.
In this writeup, I take a step back and reflect on Istio, in the context of that evolution.
The nature of how we build applications has changed over time. I recall a time when software engineers were mostly concerned with organization of logic within a process. Many software frameworks rose, targeting specific programming languages, to facilitate the organization and construction of web applications. Whether it was the Spring framework in the Java ecosystem, Ruby on Rails, NodeJS, or the myriad of Python-based frameworks that are available, to name a few.
These frameworks introduced concepts and patterns that made their ecosystem more mature, and that streamlined the practice of building applications.
To build cloud-scale systems, we had to venture out of process, and the practice of DevOps arose in part to recognize that engineering systems required a multidisciplinary approach that included infrastructure management and automation — that it’s as important to understand networking, protocols, load balancers, reverse proxies, and the many cloud services that have emerged, as components of our systems.
We ventured away from the comfort of the known programming environments towards the more adventurous (and perhaps also more rewarding) world of cloud-native platforms and microservice architectures. We moved from a mature environment to one where many of the concepts we left behind had to be re-invented, slowly, over a long period of time.
At some point it occurred to me that the cloud-native ecosystem is a large-scale effort to reconstruct many of the patterns and features that we’d already built inside those frameworks: to make those patterns accessible at a new scale of resolution. We are in a sense building ourselves a bigger box, one that can run applications at cloud-scale.
Istio supplies an important piece of the cloud-native puzzle, as I’m about to elaborate below.
Kubernetes: a Bigger Box
We can make an analogy, a comparison between the actors in a monolithic application and the actors in a cloud-native application, as follows:
- Workloads in the cloud-native world are akin to objects in a monolith.
- Communication between workloads in a Kubernetes cluster are akin to method calls between objects inside a monolith.
That analogy can be taken further, for example, a stack trace in a monolithic application is akin to a distributed trace in a cloud-native application.
Of course it’s important to remember that workloads are nothing like objects, and network calls are nothing like method calls. Each is its own animal, so to speak, with its own set of characteristics and issues.
But it’s useful to think of a cloud-native application as some kind of “bigger box” that can allow systems to run at scale, by magnifying each “object” in the monolith into a new creature, the microservice.
In a similar fashion, Istio in a cloud-native app is like dependency injection (DI) in the monolith.
If Istio is like dependency injection, then let’s compare how DI works both in Istio and in a monolith with, say, one of the most popular DI frameworks of all, the Spring framework.
Let us begin with the monolith.
Dependency Injection in the Monolith
In Spring, you define an object, aka a “bean.” The class definition has a constructor with arguments. It is the job of the application context to furnish each argument (thus freeing the class of the burden of knowing too much about how its environment is constructed). This list of arguments is basically the information the application context needs in order to construct the object. It’s a convention.
From this information, the application context builds a dependency tree. And so it knows which objects to construct first. But I digress..
The important thing to note is that an object is only given references to objects it declares it needs.
Even though the application context may maintain references to hundreds of objects, each object is given references only to its collaborators.
For example, we can imagine a ProductPage
bean, as follows:
@Component
class ProductPage {
// We used to have to annotate these with @Autowired, but that is no longer necessary.
private final DetailsService detailsService;
private final ReviewService reviewsService;
public ProductPage(DetailsService ds, ReviewsService rs) {
this.detailsService = ds;
this.reviewsService = rs;
}
public ProductInfo getProductInfo(int productId) {
ProductDetails details = detailsService.getProductDetails(productId);
ProductReviews reviews = reviewsService.getReviews(productId);
return new ProductInfo(details, reviews);
}
...
}
In this application (this system of interacting objects), there may be other services, such as a ratings service, but the ProductPage
component (or bean) does not need to be given a reference to it, because it never interacts with it directly.
Conversely, if Spring’s applicationContext
had to give each object a reference to every other object, it would create an untenable geometric growth of object references, with most being literally useless.
“Dependency Injection” in Cloud-Native Systems
Istio has an analog to that constructor that tells the application context “I only need references to these two objects, err… I mean services.” It’s the Sidecar
resource. Except that most of the time, when we do demonstrations, or set up a simple application with only a few services, we don’t bother to define these resources. It’s simpler to just let Istio do its default thing, which is to autowire (choice of words here is intended) every service into every other service.
This has been a source of criticism of Istio, claiming that Istio does not scale beyond a few services. In the monolith the constructor provides a formal means of declaring dependencies. Moreover, a developer can hardly publish their objects without declaring their constructor.
Not so with Istio. In the cloud-native world, this boils down to having discipline.
Here is the Sidecar
resource we would create in Istio for the productpage
service (presumably published in the default namespace):
---
apiVersion: networking.istio.io/v1beta1
kind: Sidecar
metadata:
name: productpage-sidecar
namespace: default
spec:
workloadSelector:
labels:
app: productpage
egress:
- hosts:
- "./reviews.default.svc.cluster.local"
- "./details.default.svc.cluster.local"
- "istio-system/*"
Et voila. Istio will now inject only references to reviews
and details
services into productpage
workloads.
Of course we are in a cloud-native world, meaning we can scale each service horizontally, upgrade a service to a new version without incurring downtime, and Istio will make sure to keep the endpoint lists up-to-date for all clients such as the productpage
workloads, while Envoy will take care of load-balancing requests to upstream services (aka “clusters”), and applying whatever network (and other) policies you define.
By making it mandatory for services to publish their dependencies via Sidecar resources, we get a scalable Istio.
A relevant talk on the subject of auto-generating sidecar resources is Cathal Conroy’s “Scaling Istio in Large Clusters.”
Thinking a Step Further
Let’s refine our analogy between the monolith and cloud-native worlds.
Roughly, I see a loose mapping between:
Monolith | Cloud-Native |
Java | Kubernetes |
Class | Deployment |
Interface | Service |
The reason Istio defines the Sidecar
resource in the first place is because a Deployment
specification does not include this important extra information we need to answer the question: What collaborating services does this deployment talk to?
If it did, then Istio could just leverage this information and dependency injection would be performant out of the box.
I suppose the moral of the story here is that it’s worthwhile comparing concepts and constructs that exist in the older and more mature world of monoliths, as an aid to designing our bigger box: Kubernetes.
Addendum
Besides dependency injection, another potential, and valuable use of this extra metadata about services is automatic configuration of authorization policies. If we know a priori what services talk to each service, we can generate authorization policies that allow only those services to reach them.Assuming this information is specified using Sidecar
resources, we could generate authorization policies for service owners to review and apply. For example: only the productpage
service is allowed to call the details
service:
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allowed-details-clients
namespace: default
spec:
selector:
matchLabels:
app: details
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/default/sa/bookinfo-productpage"]
Conclusion
The cloud-native ecosystem has evolved and matured a great deal over the last few years with the introduction of containerization, then Kubernetes, and now service meshes.
Without these and other such technologies, building cloud-native applications would remain a tall order.
###
If you’re new to service mesh, Tetrate has a bunch of free online courses available at Tetrate Academy that will quickly get you up to speed with Istio and Envoy.
Are you using Kubernetes? Tetrate Enterprise Gateway for Envoy (TEG) is the easiest way to get started with Envoy Gateway for production use cases. Get the power of Envoy Proxy in an easy-to-consume package managed by the Kubernetes Gateway API. Learn more ›
Getting started with Istio? If you’re looking for the surest way to get to production with Istio, check out Tetrate Istio Subscription. Tetrate Istio Subscription has everything you need to run Istio and Envoy in highly regulated and mission-critical production environments. It includes Tetrate Istio Distro, a 100% upstream distribution of Istio and Envoy that is FIPS-verified and FedRAMP ready. For teams requiring open source Istio and Envoy without proprietary vendor dependencies, Tetrate offers the ONLY 100% upstream Istio enterprise support offering.
Need global visibility for Istio? TIS+ is a hosted Day 2 operations solution for Istio designed to simplify and enhance the workflows of platform and support teams. Key features include: a global service dashboard, multi-cluster visibility, service topology visualization, and workspace-based access control.
Get a Demo