Fun with Istio

Personally, I find that the spectacular misuse of a given technology is a good way to explore the limits of said technology. This is exactly what I do with the Istio service mesh. Let me share with you a not particularly useful but very fun mini-project.

Background

Before we begin, let me provide some background information.

What is Istio? Intuitively, the Istio service mesh takes over the networking of your K8s cluster. For example, service-to-service requests can be intercepted by Istio, which provides an opportunity to implement traffic management, observability, and security policies. Examples include:

  • Traffic management: we can route the request to the right subset of the other service, automatically retry failed requests, etc.
  • Observability: measure the time required to get the response, etc.
  • Security: check if the two services are even allowed to talk to each other, enforce mTLS communication, etc.

Istio ingress. Istio has its own ingress component backed by an Envoy proxy. It listens on a public IP address and routes the external requests to the right entity within the K8s cluster. For the purposes of this blog post, we need to know about three important capabilities.

  • Istio ingress can make very informed routing decisions based on the used protocol, the URI, HTTP header values, and more.
  • It can also rewrite parts of the request. E.g., we use a URI rewrite when we want to route requests made to the /api/service-a/hello ingress API endpoint to Service A’s /hello endpoint. Regular expressions can be used to describe these rewriting rules.
  • It can return static responses too. E.g., a request hits the ingress at /hello-world, we can respond with the static “Hello World!” text.

How do we configure Istio ingress? We have multiple ways to configure the Envoy proxy acting as our ingress. For this mini-project, I used the Gateway and VirtualService K8s custom resources provided by Istio. The Gateway resource tells Istio what kind of requests we are open to, and the VirtualService can contain the HTTP route rules explaining how to handle these requests.

A wild idea: loop in the routing

I was wondering: can we ask a VirtualService to route a certain endpoint to the ingress itself?

And the answer is yes. We can have a request routed to the ingress and processed again. Moreover, we can do it after slightly modifying the request. While this doesn’t sound like a great idea in production, we can leverage this loop to force Istio ingress to do simple calculations through its routing rules.

(Note that this routing happens within our cluster, and is different from an HTTP redirect. Finally, these calculations don’t require any additional backend deployments.)

In the next section, I explore the idea through an example.

Counting characters

I decided to leverage my idea from the previous section and implement a simple API that responds to any endpoint containing only “a” and “b” characters (e.g., /aaaabbbababa). It responds with the string “PASS” if it has the same number of ‘a’s as ‘b’s, and with “FAIL” otherwise. For example:

  • \aabbab -> PASS,
  • \aaabb -> FAIL.

The core of the idea is to use the URI to encode the state of the computation and use URI rewriting rules to modify it in each iteration. We will use the following URI structure:

/stack-a-<data-in-stack-a>/stack-b-<data-in-stack-b>/input-<remaining-input-characters>

(We could also prefix the URI with an explicit state name. Since this kind of disambiguation is not required for this example, we stick with simplicity.)

When a request arrives from the user, we rewrite it to this format with empty stacks: /<input-characters> -> /stack-a-/stack-b-/input-<input-characters>

From now on each iteration will take the first character of the <remaining-input-characters>. Let us assume it is the character “a”. If the stack-b has a character on top (i.e., it is not empty), we erase both characters. Otherwise, we place it in stack-a. A similar logic is applied for ‘b’s. For example:

  • Next “a”, store: /stack-a-aa/stack-b-/input-aba -> /stack-a-aaa/stack-b-/input-ba
  • Next “b”, remove: /stack-a-aaa/stack-b-/input-ba -> /stack-a-aa/stack-b-/input-a

Notice that at least one of the stacks is always empty. Each stack keeps track of the surplus of the corresponding character.

When the remaining input is the empty string, we look at the two stacks. If both of them are empty we have the same number of ‘a’s as ‘b’s, otherwise, their numbers differ. A user-friendly text response can be created using the static response feature discussed earlier.

All of these rewrites and checks can be done with regular expressions supported by Istio. The full source code is available on GitHub.

We can go one step further, and observe the internals of the working solution through the Envoy proxy logs. The following logs correspond to the /abaabbba request.

[2026-06-30T09:53:33.239Z] "GET /answer/pass HTTP/2" 200 [...] answer-pass
[2026-06-30T09:53:33.239Z] "GET /stack-a-/stack-b-/input- HTTP/2" 200 [...] empty-input--empty-stacks
[2026-06-30T09:53:33.239Z] "GET /stack-a-/stack-b-b/input-a HTTP/2" 200 [...] a-next--remove
[2026-06-30T09:53:33.239Z] "GET /stack-a-/stack-b-/input-ba HTTP/2" 200 [...] b-next--store
[2026-06-30T09:53:33.239Z] "GET /stack-a-a/stack-b-/input-bba HTTP/2" 200 [...] b-next--remove
[2026-06-30T09:53:33.239Z] "GET /stack-a-aa/stack-b-/input-bbba HTTP/2" 200 [...] b-next--remove
[2026-06-30T09:53:33.239Z] "GET /stack-a-a/stack-b-/input-abbba HTTP/2" 200 [...] a-next--store
[2026-06-30T09:53:33.239Z] "GET /stack-a-/stack-b-/input-aabbba HTTP/2" 200 [...] a-next--store
[2026-06-30T09:53:33.239Z] "GET /stack-a-a/stack-b-/input-baabbba HTTP/2" 200 [...] b-next--remove
[2026-06-30T09:53:33.239Z] "GET /stack-a-/stack-b-/input-abaabbba HTTP/2" 200 [...] a-next--store
[2026-06-30T09:53:33.238Z] "GET /abaabbba HTTP/1.1" 200 [...] start

Notice that the states are logged in reverse order. This is explained by the fact that in Envoy’s eyes, the innermost request is answered first, and the response is bubbled back through the loops created by our logic. The end of each line indicates the HTTP route rule firing in the VirtualService resource.

Bonus observation

Why do we need two stacks? One seems to be enough…

I intentionally implemented the solution with two stacks. This way, the solution demonstrates that these routing rules can read an input character-by-character while maintaining two stacks. This logic closely mimics the operation of the so-called Two-stack Pushdown Automaton (2-PDA) which is known to be Turing-complete. (The formal proof of Istio routing’s Turing-completeness is left to the reader as an exercise. :slightly_smiling_face:)

Outro

Istio is a powerful service mesh. In this blog post, we only focused on its traffic management capabilities, and only used a fraction of those capabilities. And even this fraction was enough to suggest Turing-completeness. Just imagine all the fun that is possible with these technologies.

3 Likes