<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Vasilios Syrakis - envoy</title>
    <subtitle>A simple blog made with Zola and Duckquill</subtitle>
    <link rel="self" type="application/atom+xml" href="https://vsyrakis.dev/tags/envoy/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://vsyrakis.dev"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-08-18T00:00:00+00:00</updated>
    <id>https://vsyrakis.dev/tags/envoy/atom.xml</id>
    <entry xml:lang="en">
        <title>Envoy Is More Than a Reverse Proxy</title>
        <published>2026-08-18T00:00:00+00:00</published>
        <updated>2026-08-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Vasilios Syrakis
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://vsyrakis.dev/blog/envoy-beyond-the-reverse-proxy/"/>
        <id>https://vsyrakis.dev/blog/envoy-beyond-the-reverse-proxy/</id>
        
        <content type="html" xml:base="https://vsyrakis.dev/blog/envoy-beyond-the-reverse-proxy/">&lt;p&gt;Kubernetes can put every service on the same network. It cannot make every
service agree on what a timeout means.&lt;&#x2F;p&gt;
&lt;p&gt;One team writes a Go service. Another writes a Python worker. A third keeps a
legacy Java application alive. They all make HTTP calls, but each one ends up
with a slightly different collection of timeouts, retries, TLS settings,
connection pools, and telemetry.&lt;&#x2F;p&gt;
&lt;p&gt;That is where Envoy becomes more interesting than its usual description.&lt;&#x2F;p&gt;
&lt;p&gt;Envoy is a programmable traffic boundary: a place where platform policy can
become consistent request behaviour.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #A9B1D6; background-color: #1A1B26;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;client       -&amp;gt; Envoy -&amp;gt; application&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;application  -&amp;gt; Envoy -&amp;gt; dependency&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At the ingress boundary, Envoy can authenticate, authorize, route, transform,
and observe requests before they reach an application. At the egress boundary,
it can give ordinary application HTTP calls production-grade network
behaviour.&lt;&#x2F;p&gt;
&lt;p&gt;The point is not that Envoy has a long list of features. The point is that the
same kind of policy can be applied at the boundary where traffic already has
to pass.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-request-is-already-a-program&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-request-is-already-a-program&quot; aria-label=&quot;Anchor link for: the-request-is-already-a-program&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
The request is already a program&lt;&#x2F;h2&gt;
&lt;p&gt;Consider a request arriving at a public API. Before the application runs its
business logic, someone may need to answer several questions:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Is the caller authenticated?&lt;&#x2F;li&gt;
&lt;li&gt;Is this identity allowed to access this route?&lt;&#x2F;li&gt;
&lt;li&gt;Which version of the API should receive the request?&lt;&#x2F;li&gt;
&lt;li&gt;Is this tenant over its rate limit?&lt;&#x2F;li&gt;
&lt;li&gt;Should a header be added, removed, or normalised?&lt;&#x2F;li&gt;
&lt;li&gt;What should be recorded for debugging and operations?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;These are not all application questions. Many are properties of the traffic
boundary.&lt;&#x2F;p&gt;
&lt;p&gt;Envoy processes requests through a chain of filters. A filter can inspect a
request, change it, reject it, call an external service, or produce a response
without involving the upstream application.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #A9B1D6; background-color: #1A1B26;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;request -&amp;gt; identity -&amp;gt; authorization -&amp;gt; rate limit -&amp;gt; routing -&amp;gt; application&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Some of this behaviour comes from built-in filters. Envoy can validate JWTs,
enforce RBAC rules, apply rate limits, manipulate headers, compress responses,
and emit consistent access logs and metrics. External authorization and
processing filters allow a dedicated service to participate in the request
path. Lua, WebAssembly, and native extensions cover cases that need custom
logic.&lt;&#x2F;p&gt;
&lt;p&gt;That gives platform engineers a useful seam. Common protocol-level policy can
be changed in the traffic layer instead of being copied into ten application
codebases.&lt;&#x2F;p&gt;
&lt;p&gt;The seam also needs discipline. A filter runs on the request path, so slow,
fragile, or overly clever logic there affects every request. Envoy is a good
place for identity verification, admission control, routing, normalisation,
protocol translation, and observability. It is a poor place for shopping-cart
rules or a pricing algorithm.&lt;&#x2F;p&gt;
&lt;p&gt;This is bounded computation at the boundary, not an invitation to move the
whole application into the proxy.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-outbound-call-is-also-a-policy-boundary&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-outbound-call-is-also-a-policy-boundary&quot; aria-label=&quot;Anchor link for: the-outbound-call-is-also-a-policy-boundary&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
The outbound call is also a policy boundary&lt;&#x2F;h2&gt;
&lt;p&gt;The same problem appears on the other side of an application.&lt;&#x2F;p&gt;
&lt;p&gt;The code may look like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #A9B1D6; background-color: #1A1B26;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;response = http.get(&amp;quot;https:&#x2F;&#x2F;some-dependency.example&#x2F;data&amp;quot;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;But a reliable HTTP request is not one operation. It is a bundle of decisions:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;How long may DNS resolution and connection establishment take?&lt;&#x2F;li&gt;
&lt;li&gt;How long may the complete request take?&lt;&#x2F;li&gt;
&lt;li&gt;Which connections can be reused?&lt;&#x2F;li&gt;
&lt;li&gt;Can requests share an HTTP&#x2F;2 connection?&lt;&#x2F;li&gt;
&lt;li&gt;Which failures are safe to retry?&lt;&#x2F;li&gt;
&lt;li&gt;How much backoff should happen between attempts?&lt;&#x2F;li&gt;
&lt;li&gt;How do we prevent retries from amplifying an outage?&lt;&#x2F;li&gt;
&lt;li&gt;Should an unhealthy endpoint be removed temporarily?&lt;&#x2F;li&gt;
&lt;li&gt;Where are TLS credentials and certificates managed?&lt;&#x2F;li&gt;
&lt;li&gt;Which metrics, logs, and traces describe the call?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Most mature client libraries can answer many of these questions. The problem
is that every application must answer them correctly, in every language, and
keep answering them correctly as the system changes.&lt;&#x2F;p&gt;
&lt;p&gt;In a Kubernetes environment, that inconsistency becomes an operational
problem. A Go service, a Python worker, and a legacy Java application can have
three different timeout policies and three different interpretations of a
retryable failure. One may reuse connections correctly. Another may open a new
connection for every request. A third may retry a non-idempotent operation
until an already overloaded dependency becomes a crater.&lt;&#x2F;p&gt;
&lt;p&gt;Putting Envoy on the egress path gives the platform a place to own the network
mechanism and its defaults:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #A9B1D6; background-color: #1A1B26;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;application -&amp;gt; Envoy -&amp;gt; external API or internal service&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The application still decides what it wants to call. Envoy handles the
behaviour around that call: connection pools, timeouts, selected retries,
retry budgets, circuit-breaking limits, health checks, outlier detection, TLS,
authentication, and consistent telemetry.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #A9B1D6; background-color: #1A1B26;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;application: what request should I make?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Envoy:       how should that request behave on the network?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is a separation of concerns, not a transfer of responsibility. The
application still needs an overall deadline. It still needs to know whether an
operation is safe to repeat. It still needs to handle failure. Envoy can
centralise the mechanism and the policy defaults; it cannot infer the business
meaning of a timeout.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;retries-expose-the-boundary&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#retries-expose-the-boundary&quot; aria-label=&quot;Anchor link for: retries-expose-the-boundary&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Retries expose the boundary&lt;&#x2F;h2&gt;
&lt;p&gt;Retries show both the value and the limit of this approach.&lt;&#x2F;p&gt;
&lt;p&gt;If a &lt;code&gt;GET&lt;&#x2F;code&gt; fails before receiving a response, trying another healthy endpoint
may be sensible. If a request to charge a credit card times out, no response
does not prove that the charge failed. Repeating it may charge the customer
twice.&lt;&#x2F;p&gt;
&lt;p&gt;HTTP alone cannot resolve that ambiguity. The application must use idempotent
operations or idempotency keys, and the platform must define narrow retry
conditions. Every retry must fit inside one overall deadline and a bounded
retry budget.&lt;&#x2F;p&gt;
&lt;p&gt;Otherwise the policy intended to survive an outage amplifies it.&lt;&#x2F;p&gt;
&lt;p&gt;This is why Envoy should be understood as a policy boundary, not a reliability
machine. Health checks, circuit breakers, and retries improve how a system
responds to failure. They do not remove failure, and a bad policy consistently
deployed is still a bad policy.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-kubernetes-gives-you-and-what-it-does-not&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-kubernetes-gives-you-and-what-it-does-not&quot; aria-label=&quot;Anchor link for: what-kubernetes-gives-you-and-what-it-does-not&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
What Kubernetes gives you—and what it does not&lt;&#x2F;h2&gt;
&lt;p&gt;Kubernetes provides powerful primitives for scheduling workloads, discovering
services, and controlling rollout. Those primitives do not automatically
standardise the network behaviour implemented inside each workload.&lt;&#x2F;p&gt;
&lt;p&gt;Envoy complements Kubernetes by giving platform teams a programmable data
plane. It can run at an ingress gateway, beside a workload, as an egress
gateway, or as part of a service mesh. The deployment shape can change without
changing the basic idea: traffic crosses a boundary, and policy is applied
there.&lt;&#x2F;p&gt;
&lt;p&gt;That also means Envoy does not require adopting an entire service-mesh product.
A small deployment may use static configuration in front of a few services. A
larger platform may use dynamic configuration, sidecars, gateways, and a
control plane to manage changing endpoints and policies.&lt;&#x2F;p&gt;
&lt;p&gt;A service mesh is one system built around Envoy’s data plane. Envoy remains
useful when the mesh is not the problem you are trying to solve.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;when-envoy-earns-its-place&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#when-envoy-earns-its-place&quot; aria-label=&quot;Anchor link for: when-envoy-earns-its-place&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
When Envoy earns its place&lt;&#x2F;h2&gt;
&lt;p&gt;Envoy is not automatically the right answer for every application. A single
service in one language, with a small number of stable dependencies and a
well-behaved HTTP client, may not benefit from another process in its failure
path.&lt;&#x2F;p&gt;
&lt;p&gt;Envoy becomes more compelling when networking concerns repeat across teams or
runtimes:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;security policy must be applied consistently;&lt;&#x2F;li&gt;
&lt;li&gt;services use several languages or client libraries;&lt;&#x2F;li&gt;
&lt;li&gt;upstream endpoints change dynamically;&lt;&#x2F;li&gt;
&lt;li&gt;operators need one view of inbound or outbound traffic;&lt;&#x2F;li&gt;
&lt;li&gt;mTLS and certificate rotation should not live in application code;&lt;&#x2F;li&gt;
&lt;li&gt;timeout, retry, and overload policy needs platform ownership;&lt;&#x2F;li&gt;
&lt;li&gt;request processing must change without rebuilding every application.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;There is a real cost. Envoy consumes memory and CPU, configuration can become
complicated, and every proxy becomes part of the traffic path. A sidecar per
workload increases the number of processes a team must operate.&lt;&#x2F;p&gt;
&lt;p&gt;The case for Envoy is therefore not that every application deserves a sidecar.
It is that network behaviour is shared infrastructure, even when we have
accidentally implemented it as application code.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;envoy-is-where-policy-becomes-behaviour&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#envoy-is-where-policy-becomes-behaviour&quot; aria-label=&quot;Anchor link for: envoy-is-where-policy-becomes-behaviour&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Envoy is where policy becomes behaviour&lt;&#x2F;h2&gt;
&lt;p&gt;Calling Envoy a reverse proxy focuses on the simplest thing it does: moving a
request from one socket to another.&lt;&#x2F;p&gt;
&lt;p&gt;Its more valuable role is to make the traffic around an application coherent.&lt;&#x2F;p&gt;
&lt;p&gt;On ingress, Envoy provides a programmable filter chain where common,
protocol-level computation can happen before the request reaches the service.
On egress, it wraps ordinary HTTP calls in connection management, deadlines,
bounded retries, health checking, circuit breaking, identity, encryption, and
consistent observability.&lt;&#x2F;p&gt;
&lt;p&gt;Kubernetes gives workloads a common operating environment. Envoy gives the
traffic around those workloads a place for shared policy.&lt;&#x2F;p&gt;
&lt;p&gt;That is the less obvious reason to use it: not because it forwards traffic,
but because it turns platform decisions into repeatable request behaviour.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
