41.7 Deploying Go Microservices to Kubernetes

Right, so you’ve built your beautiful, elegant Go microservice. It’s probably a tiny, efficient little stateless ninja. Now we have to drop it into the jungle that is Kubernetes, where things get eaten if they don’t know the local customs. Deploying to k8s isn’t just about making it run; it’s about making it thrive and, more importantly, making it debuggable at 3 AM when everything is on fire. The absolute bedrock of this is your Dockerfile. This isn’t just a box to ship your code; it’s the blueprint for your application’s runtime existence. We’re going to do this the right way, not the “it works on my machine” way, which in Kubernetes terms means “it fails mysteriously on everyone else’s cluster.”

41.6 Service Discovery: DNS, Consul, and Kubernetes Services

Right, let’s talk about service discovery. It’s the “okay, where the heck does this thing live now?” problem of the microservices world. You can’t just hardcode an IP address into your config file and call it a day. That service might be on its third cup of coffee and its fifth pod restart this morning, happily humming along on a completely different IP. Your code needs to be smarter than that. It needs to find its friends dynamically. Let’s break down how we do that without losing our minds.

41.5 Circuit Breakers and Retry Logic

Right, let’s talk about keeping your distributed system from setting itself on fire. You’re making calls between services over a network—a notoriously flaky piece of infrastructure invented by humans who clearly never had to debug a cascading failure at 3 AM. The two biggest ways this flakiness will bite you are: a slow or failing service taking down its callers (to whom it is now a dependency), and your own retry logic turning a minor blip into a full-blown DDoS attack against the struggling service.

41.4 Metrics with Prometheus and the promhttp Handler

Right, so you’ve got your service humming along, doing its little job, and you think it’s all going well. But how do you know? A hunch? A feeling? A user screaming in all caps in your support tickets? We can do better. We’re going to instrument this thing, which is a fancy way of saying we’re going to teach it to tattle on itself. We’re going to use Prometheus, the de facto standard for metric collection in the cloud-native world, and we’re going to do it the right way from the start.

41.3 Distributed Tracing with OpenTelemetry

Right, so you’ve got your services talking to each other. Fantastic. Now, when a request fails or performance goes sideways, you’re left staring at a dozen different logs, trying to play detective across a distributed crime scene. It’s a nightmare. This is why we don’t just build microservices; we make them observable. And the first, most powerful tool in that box is distributed tracing. It’s the single best way to see the life of a request as it bounces around your system, and OpenTelemetry (OTel for short) is the de facto standard for getting it done. It’s the CNCF’s attempt to unify this chaos, and for the most part, it’s succeeding brilliantly.

41.2 Health Checks: Liveness and Readiness Endpoints

Right, let’s talk about your service’s pulse. It’s not enough that your code compiles and your tests pass. In the chaotic, distributed world of microservices, your service needs to constantly tell the world, “I’m here, I’m okay, and I’m ready to do work.” If it can’t do that, the platform running it (Kubernetes, Nomad, etc.) will assume the worst and kill it with extreme prejudice. Health checks are our way of preventing this digital murder. We do this with two fundamental endpoints: /healthz/liveness and /healthz/readiness. They sound similar, but confusing them is a classic rookie mistake that leads to very exciting, very bad outages.

41.1 Structuring a Go Microservice

Right, let’s get our hands dirty. Structuring a Go microservice isn’t about picking the fanciest framework; it’s about applying Go’s philosophy of simplicity and explicitness to a distributed system. We’re going to build a service that’s easy to reason about, test, and—crucially—throw into a mesh later. Forget the 50-file boilerplate generators; we’re going to do this the direct way. First, the absolute non-negotiable: your main.go is not your application. It’s the entry point to your application. Its job is to parse flags, read config, wire up dependencies, and start the servers. That’s it. If it’s more than 30-40 lines, you’re probably doing too much in there.

— joke —

...