The previous chapter gave you an overview of Istio, what a simple installation looks like, and how to apply a Service Mesh to a sample application. In this chapter, we will dive deeper into Istio’s control plane and data plane. We will understand the role of each of these components by going through the following topics:
This chapter will help you understand the Istio control plane so you can plan the installation of control planes in a production environment. After reading this chapter, you should be able to identify the various components of the Istio control plane including istiod, along with the functionality they each deliver in the overall working of Istio.
The following diagram summarizes the Istio architecture along with the interaction between various components. We used the Ingress gateway and istio-proxy in the previous chapter so we will not go into further details on those here. We will, however, unravel some of the other components of the Istio control plane not directly depicted in the following illustration.
Figure 3.1 – Istio control plane
Before we delve into the components of the control plane, let’s first get the definition of the term out of the way – the control plane is a set of Istio services that are responsible for the operations of the Istio data plane. There is no single component that constitutes the control plane – rather, there are several.
Let’s look at the first component of the Istio control plane called istiod.
istiod is one of the Istio control plane components, providing service discovery, configuration, and certificate management. In prior versions of Istio, the control plane was made up of components called Galley, Pilot, Mixer, Citadel, WebHook Injector, and so on. istiod unifies the functionality of these components (Pilot, Galley, and Citadel) into a single binary to provide simplified installation, operation, and monitoring, as well as seamless upgrades between various Istio versions.
Let’s look at the istiod Pod running in the istio-system namespace:
$ kubectl get po -n istio-system NAME READY STATUS RESTARTS AGE istio-egressgateway-84f95886c7-5gxps 1/1 Running 0 10d istio-ingressgateway-6cb4bb68df-qbjmq 1/1 Running 0 10d istiod-65fc7cdd7-r95jk 1/1 Running 0 10d $ kubectl exec -it pod/istiod-65fc7cdd7-r95jk -n istio-system -- /bin/sh -c «ps -ef" UID PID PPID C STIME TTY TIME CMD istio-p+ 1 0 0 Mar14 ? 00:08:26 /usr/local/bin/pilot-discovery discovery --monitoringAddr=:15014 --log_output_level=default:info --domain cluster.local --keepaliveMaxServerConnectionAge 30m
You must have noticed that the Pod itself is running pilot-discovery and is based on the following image:
$ kubectl get pod/istiod-65fc7cdd7-r95jk -n istio-system -o json | jq '.spec.containers[].image' "docker.io/istio/pilot:1.13.1"
You must have also noticed that the image for the istiod Pod is different to the istio-proxy image inserted as a sidecar. The istiod image is based on pilot-discovery, whereas the sidecar is based on proxyv2.
The following command shows that the sidecar container is created from proxyv2:
$ kubectl get pod/details-v1-7d79f8b95d-5f4td -n bookinfons -o json|jq '.spec.containers[].image' "docker.io/istio/examples-bookinfo-details-v1:1.16.2" "docker.io/istio/proxyv2:1.13.1"
Now that we know that the istiod Pod is based on pilot-discovery, let’s look at some of the functions performed by istiod.
istiod watches Istio Custom Resource Definitions (CRDs) and any other Istio-related configuration being sent to the Kubernetes API server. Any such configuration is then processed and distributed internally to various subcomponents of istiod. You interact with Istio Service Mesh via the Kubernetes API server, but all interactions with Kubernetes API server are not necessarily destined for the Service Mesh.
istiod keeps an eye on various config resources, typically Kubernetes API server resources identified by certain characteristics such as labels, namespaces, annotations, and so on. These configuration updates are then intercepted, collected, and transformed into Istio-specific formats and distributed via the Mesh Configuration Protocol (MCP) to other components of istiod. istiod also implements configuration forwarding, which we will be looking at in later chapters when we do a multi-cluster installation of Istio. For now, let’s just say that istiod can also pass configurations to another istiod instance over MCP in both pull and push modes.
istiod also adds an admission controller to enforce the validation of Istio resources before they are accepted by the Kubernetes API server. In the previous chapter, we saw two admission controllers: the mutating webhook and the validation webhook.
The mutating webhook is responsible for augmenting the API calls for resources such as deployments by adding configuration for Istio sidecar injection. Similarly, the validation webhook auto registers itself with the Kubernetes API server to be called for each incoming call for Istio CRDs. When such calls to add/update/delete Istio CRDs arrive at the Kubernetes API server, they are passed to the validation webhook, which then validates the incoming request and, based on the outcome of the validation, the API calls are accepted or rejected.
Istio provides comprehensive security for all communication in the mesh. All Pods are assigned an identity through the Istio PKI with x.509 key/cert in Spifee Verifiable Identity Document (SVID) format. The Istio Certificate Authority (CA) is responsible for signing requests from node agents deployed along with istio-proxy. The Istio CA is built on top of Citadel and is responsible for approving and signing the Certificate signature requests(CSRs) sent by Istio node agents. The Istio CA also performs the rotation and revocation of certificates and keys. It offers pluggability of different CAs as well as the flexibility to use the Kubernetes CA.
Some of the other functions and components of the Istio control plane are as follows:
istiod is a key control plane component performing many key functions of the control plane, but is not the only control plane component worth remembering. In the next section, we will examine other components that are not part of istiod but still important components of the Istio control plane.
The Istio operator and istioctl are both control plane components and are optional to install. Both provide administrative functions to install and configure components of the control and data planes. You have used istioctl quite a lot in the previous chapter as a command-line tool to interact with the Istio control plane to pass on instructions. The instructions can be to fetch information and create, update, or delete a configuration related to the workings of the Istio data plane. The Istio operator and istioctl essentially perform the same functions with the exception that istioctl is explicitly invoked to make a change, whereas the Istio operator functions per the operator framework/pattern of Kubernetes.
We will not be using the Istio operator, but if you want, you can install it using the following command:
$ istioctl operator init Installing operator controller in namespace: istio-operator using image: docker.io/istio/operator:1.13.1 Operator controller will watch namespaces: istio-system Istio operator installed Installation complete
The two main components of the Istio operator are the customer resource called IstioOperator, represented by high-level APIs, and a controller that has logic to transform the high-level API into low-level Kubernetes actions. The IstioOperator CRD wraps a second component called IstioOperatorSpec, a status field, and some additional metadata.
You can use the following command to find details of the IstioOperator Custom Resource (CR):
$ kubectl get istiooperators.install.istio.io -n istio-system -o json
You can find the output of the command here: https://github.com/PacktPublishing/Bootstrap-Service-Mesh-Implementations-with-Istio/blob/main/Output%20references/Chapter%203/IstioOperator%20CR.docx
As you can see in the output, the API is structured in line with the control plane components around the base Kubernetes resources, pilot, Ingress and Egress gateways, and finally, the optional third-party add-ons.
Figure 3.2 – The Istio operator
The preceding diagram describes the operations of the IstioOperator, while the following describes the operations of istioctl:
Figure 3.3 – istioctl
istioctl and the operator are very similar to each other except when in the Actuation phase. istioctl is a user-run command that takes an IstioOperator CR as input, while the controller runs whenever the in-cluster IstioOperator CR changes, but the remaining components are similar, if not the same.
The following is a brief summary of the various components of the Istio operator and istioctl:
Steps to uninstall IstioOperator
As we will not be using IstioOperator for the rest of book, I suggest uninstalling it using the following commands:
$ istioctl operator remove
Removing Istio operator...
Removed Deployment:istio-operator:istio-operator.
Removed Service:istio-operator:istio-operator.
Removed ServiceAccount:istio-operator:istio-operator.
Removed ClusterRole::istio-operator.
Removed ClusterRoleBinding::istio-operator.
Removal complete
$ kubectl delete ns istio-operator
namespace "istio-operator" deleted
We briefly looked at the istio-proxy in the previous chapter. In the next section, we will examine the Istio agent, which is one of the containers deployed in the istio-proxy.
The Istio agent (also called pilot-agent) is part of the control plane deployed in every istio-proxy to help connect to the mesh by securely passing configuration and secrets to the Envoy proxies. Let’s look at the istio-agent in one of the microservices of bookinfo by listing all running process in the istio-proxy sidecar of details-v1:
$ kubectl exec -it details-v1-7d79f8b95d-5f4td -c istio-proxy -n bookinfons --/bin/sh -c "ps -ef" UID PID PPID C STIME TTY TIME CMD istio-p+ 1 0 0 Mar14 ? 00:02:02 /usr/local/bin/pilot-agent p istio-p+ 15 1 0 Mar14 ? 00:08:17 /usr/local/bin/Envoy -c etc/
You must have noticed that pilot-agent is also running within the sidecar. pilot-agent not only bootstraps the Envoy proxy but also generates key and certificate pairs for Envoy proxies to establish the identity of Envoy proxies during mesh communication.
Before we talk about the role of the Istio agent in certificate generation, let’s just briefly talk about the Istio Secret Discovery Service (SDS). The SDS simplifies certificate management and was originally created by the Envoy project to provide a flexible API to deliver secrets/certificates to the Envoy proxy. The components needing the certificates are called SDS clients, and the component generating the certificates is called the SDS server. In the Istio data plane, the Envoy proxy acts as an SDS client and the Istio agent acts as the SDS server. The communication between the SDS client and SDS server happens using the SDS API specifications, mostly implemented over gRPC.
The following steps, also illustrated in Figure 3.4, are performed between the Istio agent, Envoy, and istiod to generate the certificate:
Figure 3.4 – Certificate generation for Envoy communication
In this and prior sections, we covered the Istio control plane. Now it’s time to go through various options to deploy the Istio control plane.
In the previous chapters, we installed Istio on minikube, which is one local cluster meant for development purposes on your local workstation. When deploying Istio in enterprise environments, the deployment will be not on minikube but rather on an enterprise-grade Kubernetes cluster. The Service Mesh might run on one Kubernetes cluster or be spread across multiple Kubernetes clusters. It might also be the case that all services will be on one network or may be on different networks with no direct connectivity between them. Every organization will have a different network and infrastructure disposition, and the deployment model for Istio will change accordingly.
What is a cluster?
There are many definitions of a cluster depending on what context they are being referred to. In this section, when we say cluster, we are basically referring to a set of compute nodes hosting containerized applications interconnected with each other. You can also think of a cluster as a Kubernetes cluster.
We will be discussing various architecture options for Istio in Chapter 8, but for now, let’s just briefly go through various deployment models for the control plane.
All sidecar proxies across all namespaces in the cluster connect to the control plane deployed in the same cluster. Similarly, the control plane is watching, observing, and communicating with the Kubernetes API server and sidecars within the same cluster where it is deployed.
Figure 3.5 – The data plane and control plane residing in the same Kubernetes cluster
The preceding illustration describes the deployment model we used in the previous chapter to deploy Istio. From the diagram, you can see that the Istio control plane and data plane both reside in the same Kubernetes cluster; in our case, it was minikube. istiod is installed in the istio-system namespace or any other namespace of your choosing. The data plane comprises various namespaces where applications are deployed along with istio-proxy sidecars.
A Service Mesh cluster, where the data plane and control plane are deployed in the same Kubernetes cluster, is also called a primary cluster. A cluster where the control plane is not collocated with the data plane is called a remote cluster.
In this architecture, there is a primary cluster and a remote cluster both sharing a common control plane. With this model, additional configuration is required to provide interconnectivity between the control plane in the primary cluster and the data plane in the remote cluster. The connectivity between the remote cluster and primary cluster control plane can be achieved by adding an Ingress gateway to protect and route communication to the primary control plane. This is shown in the following diagram:
Figure 3.6 – Uni-cluster control plane with data plane spread across multiple Kubernetes clusters
The Istio control plane also needs to be configured to establish the following communications:
In this configuration, instead of running a primary cluster with the control and data planes collocated on the same Kubernetes cluster, you can separate them from each other. This is done by deploying the control plane remotely on one Kubernetes cluster and having the data plane deployed on its own dedicated Kubernetes cluster. This deployment can be seen in the following diagram:
Figure 3.7 – The control plane and data plane residing in separate Kubernetes clusters
For security, separation of concerns, and compliance requirements such as Federal Information Processing Standards (FIPS), we can be required to deploy the Istio control plane separately from the data plane. Separating the control plane from the data plane allows the enforcement of strict traffic and access policies for control plane traffic without impacting the traffic flow of the data plane. Also, in an enterprise environment, where you have teams who can provide control planes as a managed service to project teams, then this model of deploying the control plane is highly suitable.
So far, the deployment models we have discussed reside over one or many Kubernetes clusters within a shared network. Where the network is not shared, the deployment model becomes more complex. We will go through some of those deployment models, along with the ones we covered in this chapter, with some hands-on exercises in Chapter 10.
In the next section, we will look at the Istio data plane and we will do that by understanding Envoy.
Envoy is the key component of the Istio data plane. To understand the Istio data plane, it is important to understand and know about Envoy.
Envoy is an open source project and CNCF graduate. You can find more details about Envoy as a CNCF project at https://www.cncf.io/projects/Envoy/. In this section, we will learn about Envoy and why it was selected as the service proxy for the Istio data plane.
Envoy is a lightweight, highly performant Layer 7 and Layer 4 proxy with an easy-to-use configuration system, making it highly configurable and suitable for serving as a standalone edge-proxy in the API gateway architecture pattern, as well as running as a sidecar in the Service Mesh architecture pattern. In both architecture patterns, Envoy runs in its own single process alongside the applications/services, which makes it easier to upgrade and manage and also allows Envoy to be deployed and upgraded transparently across the entire infrastructure.
To understand Envoy, let’s look at the following three distinctive features that make Envoy different from other proxies available today.
One of the highlights of the Envoy architecture is its unique threading model. In Envoy, the majority of the threads run asynchronously without blocking each other. Instead of having one thread per connection, multiple connections share the same worker thread running in non-blocking order. The threading model helps to process requests asynchronously but in a non-blocking manner, resulting in very high throughput.
Broadly, Envoy has three types of threads:
Another highlight of the Envoy architecture is its filter architecture. Envoy is also an L3/L4 network proxy. It features a pluggable filter chain to write filters to perform different TCP/UDP tasks. A filter chain is basically a set of steps where the output from one step is fed into the input of the second step, and so on, just as with pipes in Linux. You can construct logic and behavior by stacking your desired filters to form a filter chain. There are many filters available out of the box to support tasks, such as raw TCP proxy, UDP proxy, HTTP proxy, and TLS client cert authentication. Envoy also supports an additional HTTP L7 filter layer. Through filters, we can perform different tasks, such as buffering, rate limiting, routing, forwarding, and so on.
Envoy supports both HTTP 1.1 and HTTP 2 and can operate as a transparent proxy in both HTTP protocols. This is particularly useful when you have legacy applications that support HTTP 1.1, but when you deploy them alongside Envoy proxy, you can bridge the transformation – meaning the application can communicate over HTTP 1.1 with Envoy, which then uses HTTP 2 to communicate with others. Envoy supports a comprehensive routing subsystem that allows a very flexible routing and redirection functionality, making it suitable for building Ingress/Egress API gateways as well as being deployed as a proxy in the sidecar pattern.
Envoy also supports modern protocols such as gRPC. gRPC is an open source remote procedure call framework that can run anywhere. It is widely used for service-to-service communication and is very performant and easy to use.
The other highlight of Envoy is how it can be configured. We can configure Envoy using static configuration files that describe the services and how to communicate with them. For advanced scenarios where statically configuring Envoy would be impractical, Envoy supports dynamic configuration and can automatically reload configuration at runtime without needing a restart. A set of discovery services called xDS can be used to dynamically configure Envoy through the network and provide Envoy information about hosts, clusters HTTP routing, listening sockets, and cryptographic material. This makes it possible to write different kind of control planes for Envoy. The control plane basically implements the specification of xDS API and keeps up-to-date information of various resources and information required to be fetched dynamically by Envoy via xDS APIs. There are many open source control plane implementations for Envoy; a couple are linked as follows:
Various Service Mesh implementations such as Istio, Kuma, Gloo, and so on., which use Envoy as a sidecar, implement xDS APIs to provide configuration information to Envoy.
Envoy also supports the following:
We discussed earlier how filter chain-based architecture is one of the differentiating features of Envoy. Now let’s read about those filters that make up the filter chain.
HTTP is one of the most common application protocols, and it’s not unusual for the majority of a given workload to operate over HTTP. To support HTTP, Envoy ships with various HTTP-level filters.
When configuring Envoy, you will have to deal primarily with the following configurations:
Important note
We will now be using Docker to play with Envoy. If you are running minikube, it will be a good idea to stop minikube now. If you don’t have Docker, you can install it by following the instructions at https://docs.docker.com/get-docker/.
Armed with the knowledge we’ve obtained so far, let’s go and create some Envoy listeners.
Download the envoy Docker image:
$ docker pull envoyproxy/envoy:v1.22.2
Once you have pulled the Docker image, go ahead and run the following from the Git repository of this chapter:
docker run –rm -it -v $(pwd)/envoy-config-1.yaml:/envoy-custom.yaml -p 9901:9901 -p 10000:10000 envoyproxy/envoy:v1.22.2 -c /envoy-custom.yaml
In the preceding command, we are mounting the envoy-config-1.yaml file as a volume and passing it to the Envoy container with the -c option. We are also exposing 10000 to the localhost, which is mapped to port 10000 of the Envoy container.
Let’s now check the contents of envoy-config-1.yaml.The root of Envoy configuration is called bootstrap configuration. The first line describes whether it is in static or dynamic configuration. In this instance, we are proving a static configuration by specifying static_resources:
Static_resources: listeners: - name: listener_http
In this instance, the configuration is very straightforward. We have defined a listener called listener_http, which is listening on 0.0.0.0 and port 10000 for incoming requests:
Listeners: - name: listener_http address: socket_address: address: 0.0.0.0 port_value: 10000
We have not applied any filter specific to the listener, but we have applied a network filter called HTTPConnectionManager, or HCM:
Filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: chapter3-1_service
The HCM filter is capable of translating raw bytes into HTTP-level messages. It can handle access logging, generate request IDs, manipulate headers, manage route tables, and collect statistics. Envoy also supports defining multiple HTTP-level filters within the HCM filter. We can define these HTTP filters under the http_filters field.
In the following configuration, we have applied an HTTP router filter:
http_filters: - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router route_config: name: my_first_route_to_nowhere virtual_hosts: - name: dummy domains: ["*"] routes: - match: prefix: "/" direct_response: status: 200 body: inline_string: "Bootstrap Service Mesh Implementations with Istio"
The router filter is responsible for performing routing tasks and is also the last filter to be applied in the HTTP filter chain. The router filter defines the routes under the route_config field. Within the route configuration, we can match the incoming requests by looking at metadata such as the URI, headers, and so on., and based on that, we define where the traffic should be routed or processed.
A top-level element in routing configuration is a virtual host. Each virtual host has a name that’s used when emitting statistics (not used for routing) and a set of domains that get routed to it. In envoy-config-1.yaml, for all requests, irrespective of the host header, a hardcoded response is returned.
To check the output of envoy-config1.yaml, you can use curl to test the response:
$ curl localhost:10000 Bootstrap Service Mesh Implementations with Istio
Let’s manipulate the virtual host definition in route_config of envoy-config1.yaml with the following:
route_config: name: my_first_route_to_nowhere virtual_hosts: - name: acme domains: ["acme.com"] routes: - match: prefix: "/" direct_response: status: 200 body: inline_string: "Bootstrap Service Mesh Implementations with Istio And Acme.com" - name: ace domains: ["acme.co"] routes: - match: prefix: "/" direct_response: status: 200 body: inline_string: "Bootstrap Service Mesh Implementations with Istio And acme.co"
Here, we have defined two entries under virtual_hosts. If an incoming request’s host header is acme.com, then the routes defined in the acme virtual host will get processed. If the incoming request is destined for acme.co, then the routes defined under the ace virtual host will get processed.
Stop the Envoy container and restart it using the following commands:
docker run –rm -it -v $(pwd)/envoy-config-1.yaml:/envoy-custom.yaml -p 9901:9901 -p 10000:10000 envoyproxy/envoy:v1.22.2 -c /envoy-custom.yaml
Check the output by passing different host headers to curl:
$ curl -H host:acme.com localhost:10000 Bootstrap Service Mesh Implementations with Istio And Acme.com $ curl -H host:acme.co localhost:10000 Bootstrap Service Mesh Implementations with Istio And acme.co
In most cases, you will not send a hardcoded response to HTTP requests. Realistically, you will want to route requests to real upstream services. To demonstrate this scenario, we will be making use of nginx to mock a dummy upstream service.
Run the nginx Docker container using the following command:
docker run -p 8080:80 nginxdemos/hello:plain-text
Check the output from another terminal using curl:
$ curl localhost:8080 Server address: 172.17.0.3:80 Server name: a7f20daf0d78 Date: 12/Jul/2022:12:14:23 +0000 URI: / Request ID: 1f14eb809462eca57cc998426e73292c
We will route the request being processed by Envoy to nginx by making use of cluster subsystem configurations. Whereas the Listener subsystem configurations handle downstream request processing and managing the downstream request life cycle, the cluster subsystem is responsible for selecting and connecting the upstream connection to an endpoint. In the cluster configuration, we define clusters and endpoints.
Let’s edit envoy-config-2.yaml and modify the virtual host for acme.co with the following:
- name: ace domains: ["acme.co"] routes: - match: prefix: "/" route: cluster: nginx_service clusters: - name: nginx_service connect_timeout: 5s load_assignment: cluster_name: nginx_service endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 172.17.0.2 port_value: 80
We have removed the direct_response attribute and replaced it with the following:
route: cluster: nginx_service
We have added cluster to the definition, which sits at the same level as the listener configuration. In the cluster definition, we defined the endpoints. In this case, the endpoint is the nginx Docker container running on port 80. Please note that we are assuming that both Envoy and nginx are running on the same Docker network.
You can find the IP of the nginx container by inspecting the container. The config is saved in envoy-config-3.yaml. Please update the address value with the correct IP address of the nginx container and run the Envoy container with the updated envoy-config-3.yaml:
$ docker run –rm -it -v $(pwd)/envoy-config-3.yaml:/envoy-custom.yaml -p 9901:9901 -p 10000:10000 envoyproxy/envoy:v1.22.2 -c /envoy-custom.yaml
Perform the curl test and you will notice the response for the request destined for acme.co is coming from the nginx container:
$ curl -H host:acme.com localhost:10000 Bootstrap Service Mesh Implementations with Istio And Acme.com $ curl -H host:acme.co localhost:10000 Server address: 172.17.0.2:80 Server name: bfe8edbee142 Date: 12/Jul/2022:13:05:50 +0000 URI: / Request ID: 06bbecd3bc9901d50d16b07135fbcfed
Envoy provides several built-in HTTP filters. You can find the complete list of HTTP filters here: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/http_filters#config-http-filters.
We read previously that the listener subsystem handles the processing of incoming requests and the response to and from downstream systems. In addition to defining which addresses and ports Envoy listens on for incoming requests, we can optionally configure each listener with listener filters. The listener filters operate on newly accepted sockets and can stop or subsequently continue execution to further filters.
The order of the listener filters matters, as Envoy processes them sequentially right after the listener accepts a socket and before the connection is created. We use results from the listener filters to do filter matching to select appropriate network filter chains. For example, using a listener filter, we can determine the protocol type, and based on that, we might run specific network filters related to that protocol.
Let’s look at a simple example of listener filters in envoy-config-4.yaml under listener_filters. You will notice that we are using envoy.filters.listener.http_inspector of the following type: type.googleapis.com/envoy.extensions.filters.listener.http_inspector.v3.HttpInspector.
The HTTPInspector listener filter can detect the underlying application protocol and whether it is HTTP/1.1 or HTTP/2. You can read more about the HTTPInspector listener filter here: https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/listener_filters/http_inspector.
In this example, we are using the listener filter to find the application protocol via the filter chain. Depending on which HTTP protocol is used by the downstream system, we then apply a variety of HTTP filters, as discussed in previous sections.
You can find this example in the envoy-config-4.yaml file. Go ahead and apply the configuration to Envoy, but do also remember to close down the Docker containers you created for previous examples:
$ docker run –rm -it -v $(pwd)/envoy-config-4.yaml:/envoy-custom.yaml -p 9901:9901 -p 10000:10000 envoyproxy/envoy:v1.22.2 -c /envoy-custom.yaml
Perform curl with the HTTP 1.1 and HTTP 2 protocols, and you will see that Envoy is able to figure out the application protocol and route the request to the correct destination:
$ curl localhost:10000 –http1.1 HTTP1.1 $ curl localhost:10000 –http2-prior-knowledge HTTP2
As I mentioned earlier when introducing Envoy, it is highly configurable and can be configured dynamically. I believe the dynamic configurability of Envoy is what makes it so popular and makes it standout from the other proxies available today. Let’s look more into this next!
So far, in our previous examples, we have been using static configuration by specifying static_resources at the beginning of the config file. Every time we wanted to change the config, we had to restart the Envoy container. To avoid this, we can make use of dynamic configuration, where Envoy dynamically reloads the configuration either by reading it from disk or over the network.
For dynamic configuration where Envoy fetches the configuration over the network, we need to make use of xDS APIs, which are basically a collection of various service discovery APIs related to various Envoy configurations. To make use of xDS APIs, you need to implement an application that can fetch the latest values of various Envoy configurations and then present them via gRPC # as per the xDS protobuf specifications (also called protocol buffers; you can find details about protocol buffers at https://developers.google.com/protocol-buffers, and more on gRPC at https://grpc.io/). This application is commonly referred to as the control plane. The following diagram describes this concept.
Figure 3.8 – Control plane implementation of the xDS API
Let’s see what the service discovery APIs provide:
We covered Envoy filters in the previous section, but note that you are not limited to the built-in filters – you can easily build new filters, as we’ll see in the next section.
The filter architecture of Envoy makes it highly extensible; you can make use of various filters from the filter library as part of the filter chain. When you need some functionality not available in the filter library, then Envoy also provides the flexibility to write your own custom filter, which is then dynamically loaded by Envoy and can be used like any other filter. By default, Envoy filters are written in C++, but they can also be written using Lua script or any other programming language compiled into WebAssembly (Wasm).
The following is a brief description of all three options currently available for writing Envoy filters:
The ability to write custom filters makes Envoy extensible enough to implement for custom use cases. Support for Wasm-based filters brings down the learning curve of writing new filters as you can use a language you are most comfortable with. We hope that with the growing adoption of Envoy, there will be more tooling available to the developer to easily extend it using custom filters.
This chapter provided you with details about Istio control plane components including istiod and its architecture. We also read about the Istio operator, the CLI, and how certificate distribution works. The Istio control plane can be deployed in various architecture patterns, and we had an overview of some of these deployment patterns as well.
After covering the Istio control plane, we read about Envoy, a lightweight, highly performant l3/l4/l7 proxy. It provides a range of configurations via the listener and cluster subsystems to control request processing. The filter-based architecture is easy to use, as well as extensible, as new filters can be written in Lua, Wasm, or C++ and can easily be plugged into Envoy. Last but not least is the ability of Envoy to support dynamic configuration via xDS APIs. Envoy is the best choice for the Istio data plane because of its flexibility and performance when serving as a proxy, as well as its easy configurability via xDS APIs, which are implemented by the Istio control plane. The istio-proxy, as discussed in the previous chapter, is made up of Envoy and the Istio agent.
In the next chapter, we will put Istio to one side and instead immerse ourselves in experiencing a real-life application. We will take the application to a production-like environment and then discuss the problems that engineers would face in building and operating such an application. In Part 2 and Part 3 of this book, we will make use of this application in hands-on exercises. So, sit tight and brace yourselves for the next chapter.