A better way to describe is "smart pipes, dumb programs". Imagine that all your circuit-breaking/retry/etc robustness logic was moved into another process that happened to be running right next to the program actually doing the work.
You can have both an API gateway and a service mesh deployment -- for example Kong's Service Mesh[2] works this way. They're saying stuff like "inject gateway functionality in your service", but that only make sense if you sent literally every request (whether intra-service or to/from the outside world) through the gateway. Maybe that's how some people used Kong but I don't think everyone thought of API gateways as a place to send every single request through. You'll have a Kong API gateway at the edge and the kong proxies (little programs that you send all your requests through) next to every compute workload.
[0]: https://linkerd.io/
[1]: https://istio.io/
Imagine that for every application there is one small binary that runs and serves all it's traffic, like a chauffeur. Your application stops talking to the outside world completely and sends all messages to the small chauffeur binary -- which then talks to other chauffeurs, over the network.
Keeping with the chauffeur analogy, there is a "head office" which calls the chauffeurs on CB radio at regular intervals that lets them know which cars go where and how to start them/etc.
"head office" => "control plane"
"chauffeur" => "side-car proxy"/"data plane"
In the end what this means for your application is that you just make calls to external services (whether your own or others) and since all your communication goes through this other binary, you get monitoring, traffic shaping, enhanced security, and robustness for free.
Another interesting feature is that if the side-car proxy can actually understand your traffic, it can do even more advanced things. For example you can prevent `DELETE`s from being sent to Postgres instances at the network level.
Service meshes are proxies deployed alongside all the services, and used as much by devops (and potentially security) as by the application developers.
It seems like a very thin API Gateway that forwards calls directly to the microservice API without enforcing much would be much easier to manage and have all the same benefits.
Whats the purpose of deploying the reverse proxy alongside the microservice?
The idea behind a service mesh is that no matter how you design and implement a microservice architecture, you still have all these little services talking to each other somehow. By "default", they're communicating using native sockets and HTTP or gRPC.
There's lots that can go wrong any time you have a bunch of things talking to each other on a network (see Tanenbaum's "Critique of RPCs" for a classic explanation).
If everything was written in the same language, what you'd probably do is come up with a common networking/RPC library for all your services to use. That library would give you a common interface, so everything used the network the same way, and some measure of observability (maybe just by doing a standard log format), and maybe some security controls like a standard HTTPS connection and a standard token format.
But if you've got multiple languages, that option isn't very attractive anymore, because even if it's feasible to build that library once for each platform, now you're keeping an additional component (the library) in sync between the platforms, and that's a drag.
The insight behind a service mesh is that containers make it cheap and easy to just stack a tiny out-of-process proxy alongside your services. So you can just move all the logic you would have had in that common service library into the proxy. The proxy will give you the same features no matter whether your service is a Rust binary, Clojure running on the JVM, or a shell script.
And, because it's an out-of-process standalone component, it can be built and maintained by a third party, which means the features it provides can be a lot more ambitious than you'd build in your own library. So now instead of just hoping to get some TLS and maybe a standard token, everything can be mTLS with client certificates, a standard dashboard for managing certificates, rule systems for what client certs will allow you to talk to which systems, graphical maps of who's communicating with who and trace collection for specific pairs of services, etc.
This is all very un- like what an API gateway does, and the whole concept sort of revolves around having little reverse proxies running alongside the services.
If it were in-kernel and supported scatter-gather and zero-copy, that might be different (though some people even avoid going through the kernel).
I still think describing the concept more plainly would help a lot of the confusion.
A more general way to think about service meshes is that the network layer we code to right now is actually really primitive; its service model was fixated in the 1980s, and its programming interface hasn't much evolved from the early 1990s. We'd be happier if we could level up the whole network, so that it had QoS controls, a really expressive security model that didn't rely on magic-number ports and address ranges, and observation capabilities that communicated application-layer details and didn't just try to approximate them the way flow logs do. You can get all that stuff, internally at least, by putting all your services on the same service mesh.
Another thing to look at is Slack's Nebula, which was just released last week:
https://slack.engineering/introducing-nebula-the-open-source...
Nebula is a service mesh that runs at the IP layer (where Istio and Linkerd ride on top of HTTPS proxies, Nebula rides on top of a somewhat Wireguard-ish VPN). Slack has been using it internally for 2 years now. It's solving the same problems Linkerd is, but with a radically different implementation. You can get your laptop connected to a Nebula service mesh in ways that would be clunky to do with a Linkerd mesh.
I'm not sure what you mean by "instances had a local HAProxy running" but if you're thinking about a bunch of reverse proxies handling incoming requests, be aware that service meshes are handling both inbound _and_ outbound traffic to/from your service. For example, you might use a reverse proxy in front of your instance to terminate TLS, but you cannot implement something like two-way TLS authentication between your services unless you're putting something in at the client side as well.
I had trouble finding this under the given title. For those searching, I believe tptacek is referring to Tanenbaum & Renesse, A Critique of the Remote Procedure Call Paradigm [0], published in 1987 or 1988.
A fun read, especially since "it's just the same as local" is a myth each generation gets to revisit. Compare to Waldo, Wyant, Wollrath & Kendall's A Note on Distributed Computing[1], published in 1994.
[0] https://www.win.tue.nl/~johanl/educ/2II45/2010/Lit/Tanenbaum...
[1] https://www.cc.gatech.edu/classes/AY2010/cs4210_fall/papers/...
Once you have this underlying grid of services all connecting via a service mesh - in a reliable, secure and observable way - for some of these services you want to define governance and on-boarding rules, or you want to expose them externally as products that developer can consume and you then use an API GW to do those things.
In this regards, an API GW is just a service among the services you are running inside the service mesh.