<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hossein Zolfi</title>
    <description>The latest articles on DEV Community by Hossein Zolfi (@empire).</description>
    <link>https://dev.to/empire</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1403307%2F73075bb6-daee-4ae1-ac7a-73703407f93b.jpeg</url>
      <title>DEV Community: Hossein Zolfi</title>
      <link>https://dev.to/empire</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/empire"/>
    <language>en</language>
    <item>
      <title>Your "DevOps Team" Is the Silo DevOps Was Supposed to Kill</title>
      <dc:creator>Hossein Zolfi</dc:creator>
      <pubDate>Thu, 02 Jul 2026 09:55:28 +0000</pubDate>
      <link>https://dev.to/empire/your-devops-team-is-the-silo-devops-was-supposed-to-kill-590a</link>
      <guid>https://dev.to/empire/your-devops-team-is-the-silo-devops-was-supposed-to-kill-590a</guid>
      <description>&lt;p&gt;If your org chart has a "DevOps team" sitting between the people who write the code and the people who run it, stop and look at what you actually built. You didn't add DevOps. You relabeled the old Ops silo with a trendier name and moved the same wall one desk over. The developers still throw work over it; it just has a nicer sign now.&lt;/p&gt;

&lt;p&gt;That's not a nitpick about job titles. It's the single most common way organizations think they've adopted DevOps while structurally guaranteeing they haven't. So it's worth being precise about what the term actually means, what it doesn't, and why the mistakes cluster around the same handful of patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;p&gt;DevOps is the set of principles, practices, and cultural norms that treat building software and running it in production as one continuous responsibility, instead of two departments that hand work to each other and then argue about who broke what. The name is a portmanteau of Development and Operations, and the entire point of the term is to erase the seam between them.&lt;/p&gt;

&lt;p&gt;That's the one-sentence version. Here's what it actually means day to day, why it works, and where it gets misapplied.&lt;/p&gt;

&lt;h2&gt;
  
  
  What DevOps is not
&lt;/h2&gt;

&lt;p&gt;It helps to start with the myths, because they're where most organizations go wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's not a job title.&lt;/strong&gt; Hiring a "DevOps engineer" and putting them between the developers and the infrastructure is usually just relabeling the old Ops silo with a trendier name. If one person or one team now owns "the DevOps stuff," you've reconstructed the wall you were trying to tear down. The goal isn't a new specialist role; it's that the people who build a thing also carry responsibility for how it behaves in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's not a tool or a tool stack.&lt;/strong&gt; Kubernetes, Terraform, Jenkins, Docker: none of these make an organization "do DevOps." Large-scale research comparing delivery performance across companies has found almost no correlation between the &lt;em&gt;type&lt;/em&gt; of system (legacy mainframe vs. greenfield microservices) and how well teams deliver software. The newest, trendiest architecture is no guarantee of anything if the underlying coupling and process problems aren't solved. What predicts performance is whether a team can test and deploy its own service independently, without waiting on another team's calendar; not which vendor's logo is on the yaml file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's not the absence of change control.&lt;/strong&gt; A common misreading is that DevOps means "move fast, skip the approvals." In practice, the highest-performing teams still gate every change: they just replace a slow, low-context external review board with fast, high-context peer review plus an automated pipeline that runs the same checks on every single change, every time. That combination has been shown to outperform traditional approval boards on &lt;em&gt;both&lt;/em&gt; speed and stability. Removing a bottleneck isn't the same as removing a check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's not confined to engineering.&lt;/strong&gt; The deepest version of the idea is that technology stopped being a department a business can delegate and forget. It's more like literacy or basic math, a competency every part of an organization now needs, because when the systems fail, the business fails with them, regardless of which org chart box the systems lived in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea, in one framework: flow, feedback, learning
&lt;/h2&gt;

&lt;p&gt;Most of what gets called "DevOps practice" traces back to three underlying principles. They were originally articulated for factory production lines and manufacturing flow, then translated into knowledge work, and they hold up as a mental model no matter which specific tools or ceremonies an organization uses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Optimize the whole flow, not the local parts.&lt;/strong&gt; A team that maximizes its own throughput while creating a pile of unfinished, unintegrated work for the next team downstream hasn't made anything faster. It's just moved the waiting somewhere less visible. The unit of optimization has to be the entire path from an idea to a customer actually using it, not any one stage of that path. In practice this means making all work visible, keeping batch sizes small so nothing sits half-finished for weeks, and relentlessly finding whatever single step is truly constraining the whole system's throughput, because improving anything &lt;em&gt;except&lt;/em&gt; that bottleneck is, provably, wasted effort. If your database migrations take three weeks to provision an environment, shipping code twice as fast doesn't help; the environment is the bottleneck, and it needs to be fixed first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Make problems visible immediately, and stop to fix them.&lt;/strong&gt; In a complex system, failure is not a possibility to be prevented once and forgotten: it's a constant, ambient condition to be managed. The healthy response, borrowed from manufacturing floors, is a literal or figurative cord anyone can pull: when something breaks, the team stops taking on new work and swarms the problem until it's understood and contained, rather than patching around it and moving on. This feels expensive in the moment and is cheap, because the alternative is letting small, easily-diagnosed problems compound into large, hard-to-diagnose ones. A broken build that nobody fixes for a day doesn't stay a one-line bug; it becomes three unrelated changes tangled together, each hiding the others' effects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Build a culture where learning beats blame.&lt;/strong&gt; The first two principles only function if people are willing to surface problems honestly, and that requires a culture where reporting a mistake doesn't end a career. This isn't a soft, optional add-on: it's the load-bearing precondition for the other two. A team that hides failures to protect itself politically cannot make problems visible, and a team that gets punished for pulling the stop cord will stop pulling it. The counterintuitive part is &lt;em&gt;how&lt;/em&gt; you get this culture: not by hanging a poster of values in the break room or running a workshop, but by changing what people do. Adopt blameless postmortems, protect time for fixing root causes instead of just symptoms, and the trust follows the behavior. You don't need to fix people's beliefs first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this looks like in practice
&lt;/h2&gt;

&lt;p&gt;The principles above cash out into a fairly concrete set of practices. None of them is DevOps by itself; together, they're what makes the principles real instead of aspirational.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Continuous delivery.&lt;/strong&gt; The ability to get any change, such as a feature, a config tweak, or a one-line bug fix, into production safely and quickly enough that releasing becomes routine rather than a quarterly event everyone dreads. This rests on automated testing that runs on every change, developers integrating their work into a shared mainline at least daily instead of working in long-lived branches, and treating a red build as an emergency, not background noise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loosely coupled architecture.&lt;/strong&gt; However a system is decomposed, whether services, modules, or a well-organized monolith, the property that actually matters is whether a team can change and ship its piece without needing permission or fine-grained coordination from every other team. This is consistently the single largest lever on delivery speed, larger than any specific automation tooling. Using "microservices" doesn't automatically buy this: services that all read and write the same shared data are just a monolith with extra network calls and extra latency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure as code.&lt;/strong&gt; Defining servers, networks, and configuration as version-controlled, reviewable, testable code instead of manual console clicks or one-off scripts. This turns infrastructure changes into the same kind of reviewable, revertible, auditable artifact that application code already is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment pipelines.&lt;/strong&gt; An automated, multi-stage path that every change travels from commit to production, running increasingly rigorous checks at each stage. This is where governance actually lives in a mature setup: not in a meeting where a committee eyeballs a spreadsheet, but in code that runs identically, every time, on every change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visible work and limited work-in-progress.&lt;/strong&gt; Dashboards and boards that show what's actually in flight, combined with hard limits on how much can be in flight at once. Counterintuitively, limiting work-in-progress &lt;em&gt;increases&lt;/em&gt; throughput, because a system running at 100% utilization has infinite queueing time in front of it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security built in, not bolted on.&lt;/strong&gt; Security review and automated scanning integrated into the same pipeline as everything else, rather than a separate gate that shows up right before launch with a list of findings nobody has time to fix. Teams that do this consistently show &lt;em&gt;both&lt;/em&gt; faster delivery and fewer security incidents: it isn't a speed-versus-safety trade-off, it's the same "catch it early, catch it cheap" logic applied to a different category of defect.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  It's measurable, and the measurements are counterintuitive
&lt;/h2&gt;

&lt;p&gt;One of the things that separates this body of practice from a lot of management fashion is that it has been rigorously studied at scale, across thousands of organizations, using validated survey instruments rather than anecdote. Four metrics have proven to reliably distinguish high- from low-performing organizations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lead time&lt;/strong&gt;: how long from a change being committed to it running in production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment frequency&lt;/strong&gt;: how often changes reach production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time to restore service&lt;/strong&gt;: how quickly the team recovers when something breaks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change failure rate&lt;/strong&gt;: what fraction of changes cause a problem requiring a fix.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first two describe &lt;em&gt;tempo&lt;/em&gt;; the last two describe &lt;em&gt;stability&lt;/em&gt;. The finding that keeps getting rediscovered and keeps surprising people is that these are not in tension. Organizations that are fast are also stable, and organizations that are slow are also unstable: there's no meaningful cluster of "fast but reckless" or "slow but rock-solid" teams. The old assumption that you must trade velocity for safety, and that a change-approval board and a slow release cadence buy you reliability, doesn't hold up against the data. What actually buys reliability is small changes, fast feedback, and automated verification, the same things that buy speed.&lt;/p&gt;

&lt;p&gt;A related finding worth internalizing: don't try to measure your way to a "mature" end state. A maturity model implies a fixed ladder everyone climbs in the same order to reach a finish line called "done." A capability model instead treats improvement as continuous and context-dependent, where different teams need different levers depending on where their constraint actually is, and the bar for what counts as "good" keeps rising as the whole industry improves. There is no finish line, and pretending there is one is itself a source of complacency.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it relates to nearby ideas
&lt;/h2&gt;

&lt;p&gt;A few terms get used interchangeably with DevOps that are actually more specific applications of it, worth distinguishing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Site Reliability Engineering&lt;/strong&gt; is best understood as one particular, unusually rigorous implementation of the same underlying principles, with its own vocabulary: an error budget (the acceptable amount of unreliability over a time window, spent deliberately to buy release velocity) turns "how reliable should this be" from a political argument into arithmetic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform engineering&lt;/strong&gt; is the organizational answer to the question "what happens once DevOps needs to scale past a single team?" If every team owns everything end to end, every team eventually re-solves the same infrastructure problems badly. A platform team's job is to absorb that shared complexity behind a self-service interface, run &lt;em&gt;as a product&lt;/em&gt;, with usability, a roadmap, and real users, not as a pile of internal tools nobody asked for. Done badly, a platform team becomes exactly the bottleneck DevOps was trying to remove; the discipline that keeps it from happening is building the smallest platform that actually helps, and no bigger.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DevSecOps&lt;/strong&gt; extends the same "build it in, don't bolt it on" logic to a third party at the table besides Dev and Ops: security. Same argument, same evidence pattern, one more silo dissolved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agile&lt;/strong&gt; is a close cousin but not a synonym. Agile's classic feedback loop mostly stops at "code complete": did we build the right thing, are we iterating on the backlog. DevOps picks up exactly where that loop stops and extends it through deployment and into production: does the thing actually work when real users touch it, and can we find out fast enough to matter.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common failure modes
&lt;/h2&gt;

&lt;p&gt;A few patterns show up often enough to be worth naming directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Renaming the silo instead of removing it.&lt;/strong&gt; This is the one we opened with: creating a "DevOps team" that developers throw work over the wall to is the antipattern the whole movement exists to prevent, wearing the movement's own name as camouflage. It's the single most common way an organization can point at a team, a title, and a Slack channel and genuinely believe it has "done DevOps" while the actual handoff problem is untouched.&lt;/p&gt;

&lt;p&gt;The rest of the failure modes tend to follow from the same root cause, treating DevOps as something you install rather than something you change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Buying tools before fixing coupling.&lt;/strong&gt; An organization with a tightly coupled architecture that adopts container orchestration and calls it done will find its deployment cadence barely moves, because the actual constraint, teams that can't ship without coordinating with three other teams, was never touched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treating the four metrics as a stick.&lt;/strong&gt; The moment delivery metrics get used to rank individuals or teams punitively, people start gaming them, and the numbers stop reflecting reality. They're diagnostic instruments for finding where to invest, not a scoreboard for performance reviews.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Over-building the platform.&lt;/strong&gt; Left unchecked, engineers building an internal platform will keep bolting on shelves nobody asked for, because building infrastructure scratches an itch that shipping features never quite does. The discipline is to build the thinnest thing that actually removes friction, and grow it only when a real, felt need shows up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skipping the culture work because it's the hardest to measure.&lt;/strong&gt; It's tempting to adopt the tooling and the pipeline and skip the harder, fuzzier culture piece. But the pipeline only works if people trust it enough to let a red build actually stop them, and that trust is exactly what a blame-first culture destroys.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The steel-manned objection: isn't specialization just how orgs scale?
&lt;/h2&gt;

&lt;p&gt;Here's the strongest version of the counter-argument, and it deserves a real answer rather than a strawman: don't organizations specialize as they grow precisely &lt;em&gt;because&lt;/em&gt; generalists don't scale? Nobody expects every engineer to also be a security expert or a database administrator: those are dedicated teams, and nobody calls that a silo problem. Why should "runs the infrastructure" be any different? A dedicated platform or DevOps team could just be sane division of labor, not a regression.&lt;/p&gt;

&lt;p&gt;That argument is genuinely right: under one specific condition. It wins when the specialist team operates as a &lt;strong&gt;self-service platform&lt;/strong&gt; that other teams pull from on their own schedule, the same way a team pulls in a well-documented open-source library: no ticket, no queue, no waiting for someone else's sprint. It loses the moment that team becomes a &lt;strong&gt;gate&lt;/strong&gt; other teams must line up behind to ship anything: a mandatory review, a provisioning request, a "file a ticket and we'll get to it." The difference isn't the org chart box the specialists sit in; it's whether using their work requires asking permission. A security team that ships a self-serve scanning library is fine. A security team that must personally approve every deploy is the silo again, just wearing a different department's name.&lt;/p&gt;

&lt;h2&gt;
  
  
  The underlying bet
&lt;/h2&gt;

&lt;p&gt;Strip away the vocabulary and DevOps is a bet about where value gets created: not in the moment code is written, but in the moment it's running, serving real users, generating real feedback. Everything else, the pipelines, the metrics, the culture work, the org design, exists in service of shortening the distance between "we had an idea" and "we know whether the idea was any good," as many times a day as the organization can stand. Speed and safety turn out not to be opposite ends of a dial you have to choose a position on; they're both downstream of the same thing, which is how tight that feedback loop actually is.&lt;/p&gt;

&lt;p&gt;So if you're looking at your own org chart right now: is your "DevOps team" a self-service platform other teams pull from freely, or is it a queue they wait in? That answer tells you more about whether you've actually adopted DevOps than any tool in your stack will.&lt;/p&gt;




&lt;p&gt;Look at your own org chart: is your platform team something other teams pull from freely, or something they queue behind? Drop a comment. I especially want to hear the queue stories, those are the ones worth fixing.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>platformengineering</category>
      <category>sre</category>
      <category>cicd</category>
    </item>
    <item>
      <title>The Hidden Tax You Are Paying Every Day: A Dev's Journey into Ops Automation</title>
      <dc:creator>Hossein Zolfi</dc:creator>
      <pubDate>Sun, 23 Nov 2025 05:09:02 +0000</pubDate>
      <link>https://dev.to/empire/the-hidden-tax-you-are-paying-every-day-a-devs-journey-into-ops-automation-5f80</link>
      <guid>https://dev.to/empire/the-hidden-tax-you-are-paying-every-day-a-devs-journey-into-ops-automation-5f80</guid>
      <description>&lt;p&gt;Starting a new career is kinda like walking into a workshop full of tools you've never seen before. You know they're useful, maybe even powerful, but honestly... you're not totally sure what they do or whether you'll break something the moment you touch them. That's me right now. Everything feels new. And confusing. And fun. Sometimes all at once.&lt;/p&gt;

&lt;p&gt;This article is part of a series where I'm basically thinking out loud about the stuff I'm learning. I want my future self to read these later and go like: "Oh wow, that's where I started." Or maybe he'll laugh at me. Either way, it's worth documenting.&lt;/p&gt;

&lt;p&gt;Along the way I ran into a bunch of questions. Some I answered. Some I couldn't. Some probably don't even have a real answer. I'm gonna put them here anyway because they're part of the journey.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Identify the Problem (aka "Why am I doing this manually??")
&lt;/h2&gt;

&lt;p&gt;One of the things I'm struggling with is: how do I share operations with my future self? Suppose I want to set up a client on a machine that wants to ship logs to external servers, how should I do it? How should I remember what I've done before? Reading documentation or command history to find out what I did before? Seriously. What about urgent situations: What happens if we lose a VM? How do I know what's even on these machines?&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;💰 The CEO/Manager ROI Corner&lt;/p&gt;

&lt;p&gt;If you are a manager reading this, here is the math: Manual ops is a hidden tax. Every hour I spend SSH-ing into a box to fix a config is an hour I am not building features. Plus, manual changes mean human error, which means downtime risk. Automation isn't just "cool", it is risk management.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Coming back to the ops world after doing dev work, my first thought when I saw these GCP instances was: &lt;strong&gt;how do I get rid of manual work.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Throughout my entire life as a software engineer, I've never been able to persuade myself to do repetitive work manually. I just... I can't. I hate it. If I have to do the same clicks and commands more than twice, I literally start to feel dizziness. It just feels wrong.&lt;/p&gt;

&lt;p&gt;This is basically the &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" rel="noopener noreferrer"&gt;DRY Principle (Don't Repeat Yourself)&lt;/a&gt; applied to infrastructure.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every piece of knowledge must have a single, unambiguous, authoritative representation within a system&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To save time in these kinds of situations the obvious answer is docs. Notion, Confluence, Google Docs, whatever. But... why? Just to tell other people (technical or not) what's installed? And how I set it up? Especially since not everyone can just ssh in and check.&lt;/p&gt;

&lt;p&gt;That just doesn't work for me. We're software developers. We're supposed to like code, right? We live in it. And for me, running a command from a script is always safer and faster than typing it manually. If someone else is doing this manual work, it isn't a reason I should do it. So in the middle of my work and tasks, I explore my options.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Exploring Options
&lt;/h2&gt;

&lt;p&gt;The real answer, for me, has to involve Git. A repo.&lt;/p&gt;

&lt;p&gt;My mind immediately went to Ansible. But I did pause for a second to think about the other players in the room.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Ansible&lt;/strong&gt; is open-source, command-line IT automation software written in Python. It can configure systems, deploy software, and orchestrate advanced workflows to support application deployment, system updates, and more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's exactly what I need.&lt;/p&gt;

&lt;p&gt;Here’s a quick mental matrix I went through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;The Vibe&lt;/th&gt;
&lt;th&gt;Why I picked (or didn't pick) it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Terraform&lt;/td&gt;
&lt;td&gt;The "Industry Standard" for infra. Great for creating VMs, less great for configuring inside them.&lt;/td&gt;
&lt;td&gt;Overkill for right now. I have the VMs; I need to configure the OS. The learning curve is steep when I just need to install Docker.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pulumi&lt;/td&gt;
&lt;td&gt;"Infrastructure as Code" but actual code (TS/Python).&lt;/td&gt;
&lt;td&gt;Super cool, but adds complexity I don't need yet. I want simple config files, not a compile step.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ansible&lt;/td&gt;
&lt;td&gt;The "Old Reliable". Agentless, runs over SSH.&lt;/td&gt;
&lt;td&gt;Winner. It uses YAML, handles server configuration perfectly, and I already know the basics.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
Decision Point: Why Ansible?








&lt;blockquote&gt;
&lt;p&gt;To be honest, it's just what I know. I used it years ago, so I'm familiar with it. My focus right now wasn’t to learn a brand-new provisioning tool. That just wasn't aligned with my career goals at this moment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; When you are a solo dev or a small team, &lt;strong&gt;Velocity &amp;gt; Perfection&lt;/strong&gt;. Always.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, good or not, I chose Ansible. It's my baseline. It'll tell me what I set up, and it'll help me install services in minutes instead of hours (like webhooks or configuring logs for Loki — should that take hours? No. But the context-switching kills you). And we can place the configuration in code and commit it in a Git repo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementation: OK, let's do this
&lt;/h2&gt;

&lt;p&gt;So, I chose Ansible. Now. How do I use it to provision and configure machines in GCP?&lt;/p&gt;

&lt;p&gt;This is where it got weird. The GCP infra is really different from what I'm used to. The network access is super abstract and hidden. The OS runs differently (it has agents! that configure things from the web console!).&lt;/p&gt;

&lt;p&gt;And everything is about regions and zones. You've seen the docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Regions are independent geographic areas that consist of zones. A zone is a deployment area within a region.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yada yada. Why do I care? &lt;strong&gt;Cost&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This stuff isn't free. If you're self-hosting, you just stack your servers in one datacenter. In GCP, if you have a machine in Europe and one in North America, you're &lt;a href="https://cloud.google.com/vpc/network-pricing?hl=en" rel="noopener noreferrer"&gt;paying&lt;/a&gt; for that data transfer (like $0.05 per GB).&lt;/p&gt;

&lt;p&gt;Even external IPs cost money! A static IP in Belgium is $3.65/month. It's not much, but for 10 VMs? That adds up. Even if the machine has an external IP to connect, its IP address can be ephemeral! It means if a machine stops and starts (not restarts) GCP will assign a new external IP address. Just wanting to have an external IP doesn't mean I should reserve one.&lt;/p&gt;

&lt;p&gt;This leads to a simple conclusion:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you don't need an external IP, DON'T ASSIGN ONE.&lt;/strong&gt; It's just wasted money.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: The Actual Problem
&lt;/h2&gt;

&lt;p&gt;Okay, so if I follow my own advice and don't assign an external IP how the hell does Ansible connect to it? Ansible needs an IP in its inventory file. Where can I find the IP address? Am I wrong to choose Ansible as my provisioning tool? So I have Ansible — how can I configure machines with it?&lt;/p&gt;

&lt;p&gt;This is where the &lt;strong&gt;gcloud&lt;/strong&gt; command comes in. GCP gives you this tool to manage everything. You can connect to a machine with it even without a public IP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute ssh demo &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;europe-north1-a &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's basically just magic. It tunnels through or something. I don't care how right now, just that it works.&lt;/p&gt;

&lt;p&gt;So, if gcloud can ssh maybe &lt;strong&gt;Ansible can use gcloud instead of ssh&lt;/strong&gt;?&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: A Wrapper Script
&lt;/h2&gt;

&lt;p&gt;The idea: we tell Ansible:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"When you want to ssh, run this script instead."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ansible.cfg&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[ssh_connection]&lt;/span&gt;
&lt;span class="py"&gt;ssh_executable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;gcp-ssh-wrapper.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to build &lt;code&gt;gcp-ssh-wrapper.sh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The script receives all the arguments Ansible passes to SSH and instead passes them to &lt;code&gt;gcloud compute ssh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The core looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;exec &lt;/span&gt;gcloud compute ssh &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tunnel-through-iap&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--no-user-output-enabled&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;zone&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;project&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;cmd&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get the host and command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;:&lt;span class="p"&gt; -2&lt;/span&gt;:1&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;:&lt;span class="p"&gt; -1&lt;/span&gt;:1&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To collect SSH options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;declare&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; opts
&lt;span class="k"&gt;for &lt;/span&gt;ssh_arg &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;:1:&lt;span class="nv"&gt;$#-3&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ssh_arg&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;opts+&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ssh_arg&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;fi
done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But... wait. &lt;code&gt;gcloud&lt;/code&gt; needs zone and project. Ansible doesn't know about them. So the script has to ask Ansible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ansible-inventory &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;inventory_path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--host&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; |
    jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.zone'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ansible-inventory &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;inventory_path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--host&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; |
    jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.project'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💾 Grab the Code&lt;br&gt;
Instead of copy-pasting the snippets above and hoping for the best, I've uploaded the final, working &lt;code&gt;gcp-ssh-wrapper.sh&lt;/code&gt; script &lt;a href="https://gist.github.com/empire/00e33a42cf12d1ca7148b5784dc18c4d" rel="noopener noreferrer"&gt;to this GitHub Gist&lt;/a&gt;. Download it, chmod +x it, and you are good to go.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Inventory
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# inventory/inventory.ini
&lt;/span&gt;&lt;span class="nn"&gt;[project1]&lt;/span&gt;
&lt;span class="err"&gt;demo&lt;/span&gt; &lt;span class="py"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;europe-north1-a&lt;/span&gt;

&lt;span class="nn"&gt;[project1:vars]&lt;/span&gt;
&lt;span class="py"&gt;project&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;my-project&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Let's test it
&lt;/h2&gt;

&lt;p&gt;Playbook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure Docker logging&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;gather_facts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Template Docker daemon configuration&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;templates/daemon.json.j2&lt;/span&gt;
        &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/docker/daemon.json&lt;/span&gt;
        &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
        &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
        &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0644'&lt;/span&gt;
      &lt;span class="na"&gt;notify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restart Docker&lt;/span&gt;
      &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker&lt;/span&gt;

  &lt;span class="na"&gt;handlers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restart Docker&lt;/span&gt;
      &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;restarted&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;loki_enabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"log-driver"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"loki"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"log-opts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"loki-url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ loki_url }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"loki-batch-size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"400"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"log-driver"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json-file"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"log-opts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max-size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"300m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max-file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;endif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vars:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;loki_enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;loki_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://10.166.0.20:3100/loki/api/v1/push"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-playbook &lt;span class="nt"&gt;-i&lt;/span&gt; inventory/ &lt;span class="nt"&gt;--diff&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; demo playbook_docker.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Important Note on Refinement
&lt;/h2&gt;

&lt;p&gt;Before you copy-paste &lt;a href="https://gist.github.com/empire/00e33a42cf12d1ca7148b5784dc18c4d" rel="noopener noreferrer"&gt;this&lt;/a&gt; into production and yell at me: I know this is a &lt;a href="https://en.wikipedia.org/wiki/Proof_of_concept" rel="noopener noreferrer"&gt;Proof of Concept&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'm aware that querying &lt;code&gt;ansible-inventory&lt;/code&gt; twice per connection is a performance bottleneck. I also know a true "Enterprise" setup would use the official &lt;code&gt;google.cloud.gcp_compute&lt;/code&gt; plugin for dynamic inventory and leverage Service Accounts for CI/CD.&lt;/p&gt;

&lt;p&gt;However, this approach trades 'Best Practice' complexity for immediate velocity. When you are a solo dev just trying to ship features, that is a trade worth making.&lt;/p&gt;

&lt;p&gt;That said, here is my &lt;a href="https://en.wikipedia.org/wiki/Technical_debt" rel="noopener noreferrer"&gt;Technical Debt&lt;/a&gt; list to tackle later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Speed:&lt;/strong&gt; Caching the inventory lookups to stop spawning processes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scale:&lt;/strong&gt; Handling multi-region/project setups without hardcoded zones.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Resilience:&lt;/strong&gt; Better error handling for when &lt;code&gt;gcloud&lt;/code&gt; hiccups.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;So, the job is... done? Kinda.&lt;/p&gt;

&lt;p&gt;I'm left with a few more questions to explore in the next part of this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Magic Tunnel:&lt;/strong&gt; How exactly does IAP (Identity-Aware Proxy) work under the hood?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Name Collisions:&lt;/strong&gt; What if two machines in different projects share the same name?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The "Cloudflare" Layer:&lt;/strong&gt; There are some blackbox areas regarding how this interacts with Cloudflare that I need to document.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anyway, that's where I am. It's a hack, but it's my hack, and it works.&lt;/p&gt;

&lt;p&gt;This article took nearly a month to write, test, and clean up. I'm really glad I started using Ansible. it's solving problems I didn't even know I had. If you've struggled with similar manual-ops dizziness, let me know in the comments.&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>devops</category>
      <category>career</category>
      <category>automation</category>
    </item>
    <item>
      <title>Too Much Talent: Why Building a Great Team Isn't Just About Seniority</title>
      <dc:creator>Hossein Zolfi</dc:creator>
      <pubDate>Tue, 13 May 2025 11:35:01 +0000</pubDate>
      <link>https://dev.to/empire/too-much-talent-why-building-a-great-team-isnt-just-about-seniority-2126</link>
      <guid>https://dev.to/empire/too-much-talent-why-building-a-great-team-isnt-just-about-seniority-2126</guid>
      <description>&lt;h2&gt;
  
  
  From Stars to Team
&lt;/h2&gt;

&lt;p&gt;Before I became an engineering manganager and when I was just reading software engineering books, I believed the surest path to success was hiring as many top-tier engineers as possible. My logic was simple: great software design comes from great engineers. A colleague once told me, &lt;em&gt;"Hire stars—they'll build game-changing features!"&lt;/em&gt; And often, they did. These engineers introduced powerful designs, elegant abstractions, and impressive capabilities that elevated our products.&lt;/p&gt;

&lt;p&gt;But over time, I saw the limits of that approach. Debates dragged on. Unpopular tasks went unowned. Engineers left because they felt underutilized. After reflecting on what I saw in my own teams—and what I learned from others—I realized:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Focusing solely on individual brilliance—even when it delivers great features—often sacrifices teamwork, consistency, and long-term growth.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The real magic happens when the team works as a cohesive unit. The goal isn't to collect stars—it's to build a constellation.&lt;/p&gt;




&lt;p&gt;When building teams—whether in sports or software—the instinct is to stack the roster with as much talent as possible. More senior engineers, more superstars, more wins, right? Not always. Research and real-world experience suggest a more nuanced truth: too much talent can actually hinder performance. Even when individual engineers introduce brilliant features, the lack of balance and collaboration can slow the team down. Here's why—and how to build a team that thrives.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Research: Talent Has Diminishing Returns
&lt;/h2&gt;

&lt;p&gt;A 2014 study by Swaab, Schaerer, Anicich, and Ronay analyzed highly interdependent team sports like soccer and basketball. They found that adding talent improves performance—up to a point. Beyond that, performance suffers. Too many stars can lead to coordination breakdowns, ego clashes, and poor collective results.&lt;/p&gt;

&lt;p&gt;This challenges the assumption that talent and output scale linearly. In interdependent teams—like most software engineering groups—excessive star power can actually disrupt the very collaboration needed to succeed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key takeaway:&lt;/strong&gt; Talent drives performance, but only if it doesn't undermine teamwork.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why This Matters for Software Teams
&lt;/h2&gt;

&lt;p&gt;Software engineering is a team sport. Engineers must align on architecture, review each other's work, and ship features together. A team made up solely of senior engineers or high-performers can run into serious problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decision Gridlock:&lt;/strong&gt; Strong opinions may clash over tech choices or system design, delaying progress.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Neglected Execution:&lt;/strong&gt; Critical but unglamorous tasks—like CI/CD setup, writing documentation, or fixing edge-case bugs—may get ignored.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leadership Friction:&lt;/strong&gt; Too many leaders without clear roles often compete instead of collaborate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blocked Growth:&lt;/strong&gt; Without mid-level or junior engineers, there's no natural path for mentorship or long-term team development.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Even when talented engineers ship great features, a lack of cohesion or clarity in roles can erode team efficiency and morale.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Real-World Examples: Talent Alone Isn't Enough
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Google's Early Struggles
&lt;/h3&gt;

&lt;p&gt;In the early 2010s, Google's engineering teams were packed with elite talent. Yet, some teams struggled to ship:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Endless architecture debates delayed progress.&lt;/li&gt;
&lt;li&gt;Glue work like documentation and infrastructure lagged behind.&lt;/li&gt;
&lt;li&gt;Junior engineers felt sidelined, which hurt morale and retention.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Google eventually recognized that technical brilliance wasn't enough. They began emphasizing soft skills—collaboration, humility, team orientation—in hiring and promotions. They also formalized the role of the Tech Lead to create alignment and speed up decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Node.js and Open Source Governance
&lt;/h3&gt;

&lt;p&gt;Node.js, in its early days, suffered from too many strong voices pushing in different directions. Conflicts and lack of governance stalled progress. The project rebounded only after implementing clearer roles and decision-making processes. This empowered contributors at all levels and helped the project scale.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;These examples show that raw talent is not a silver bullet. Without structure and shared purpose, even the best engineers can struggle to work effectively together.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Role of Tech Leads in Balancing Talent
&lt;/h2&gt;

&lt;p&gt;A well-balanced team still needs guidance. That's where Tech Leads come in. The Tech Lead isn't the smartest person in the room—they're the one who keeps the room aligned.&lt;/p&gt;

&lt;p&gt;Before I became a team lead, I didn't fully understand this role. In my past companies, the Tech Lead was treated as the boss—someone who went to meetings, made decisions alone, and became disconnected from the team. Often, these leads lacked strong software instincts and sometimes introduced friction with senior engineers. This was far from the kind of leadership that fosters a healthy engineering culture.&lt;/p&gt;

&lt;p&gt;Now I realize:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A Tech Lead is not a boss. A good Tech Lead is a coordinator and a listener.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The role is about creating alignment—not control. It's about ensuring that everyone contributes, conflicts are resolved constructively, and decisions move forward without dragging the team down.&lt;/p&gt;

&lt;p&gt;An effective Tech Lead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guides Without Dominating:&lt;/strong&gt; They set direction while making space for team input.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolves Conflict:&lt;/strong&gt; They facilitate decisions so the team doesn't stall.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leverages All Levels:&lt;/strong&gt; They ensure seniors mentor, mid-levels execute, and juniors grow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focuses on Outcomes:&lt;/strong&gt; They prioritize what's best for the product—not their own technical legacy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Spotify's Squad Leads operate in this mold, helping cross-functional teams stay aligned while maintaining speed and cohesion.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Without a Tech Lead—or with one driven by ego—teams of superstars often descend into dysfunction.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  How to Build a Balanced Team: Practical Tips
&lt;/h2&gt;

&lt;p&gt;If talent alone isn't the answer, what is? Here are some actionable ways to build a high-performing team:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hire T-Shaped Engineers:&lt;/strong&gt; Seek people with deep expertise in one area, but enough breadth to collaborate across functions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assess Gaps Before Hiring:&lt;/strong&gt; Need innovation? Hire a senior designer or architect. Need delivery? Mid-levels are crucial. Need culture? Add a mentor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prioritize Soft Skills:&lt;/strong&gt; Interview for humility, communication, and collaboration—not just algorithms and system design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Foster Mentorship:&lt;/strong&gt; Pair seniors with juniors. This builds culture and spreads knowledge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Empower a Tech Lead:&lt;/strong&gt; Choose someone who values team success over individual heroics. Support their growth with training in facilitation and leadership.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  A Fun Metaphor: The LeBron James Problem
&lt;/h2&gt;

&lt;p&gt;You might call this the &lt;strong&gt;LeBron James Problem&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You'll wait forever trying to hire three LeBron Jameses—they're rare and expensive.&lt;/li&gt;
&lt;li&gt;Even if you succeed, they may clash instead of cooperate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Software teams aren't basketball fantasy drafts. Obsessing over rockstar hires wastes time and breeds dysfunction. A team of role players with clear leadership often outperforms a group of misaligned all-stars.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts: Build a Constellation, Not a Cluster of Stars
&lt;/h2&gt;

&lt;p&gt;Talent absolutely matters. Superstar engineers can introduce groundbreaking features. But once you reach a certain threshold, adding more talent without balancing the team dynamic can backfire.&lt;/p&gt;

&lt;p&gt;To build a truly great team:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Seek balance:&lt;/strong&gt; Mix senior, mid-level, and junior engineers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prioritize teamwork:&lt;/strong&gt; Hire for compatibility as well as capability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support leadership:&lt;/strong&gt; Use Tech Leads to align the team and resolve friction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Value the whole:&lt;/strong&gt; Celebrate team outcomes over individual accomplishments.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Don't build a cluster of lone stars. Build a constellation—where everyone shines together.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>softwareengineering</category>
      <category>teambuilding</category>
      <category>leadership</category>
    </item>
    <item>
      <title>Remote debugging Go App</title>
      <dc:creator>Hossein Zolfi</dc:creator>
      <pubDate>Sat, 12 Oct 2024 13:59:06 +0000</pubDate>
      <link>https://dev.to/empire/remote-debugging-go-app-1nml</link>
      <guid>https://dev.to/empire/remote-debugging-go-app-1nml</guid>
      <description>&lt;p&gt;For the longest time, I wasn’t a fan of debuggers. Coming from a background in Spring Framework, Java, Python, and PHP (Symfony/Laravel), I always found logs and traces more reliable for debugging. I had even dabbled with GDB in the early days, but it didn’t stick. Instead, I relied on logging to figure out what my applications were doing.&lt;/p&gt;

&lt;p&gt;However, my perspective changed when I started working with Go. While developing in Kubernetes and working on microservices, I faced a situation where the complexity of the service made logging and tests insufficient. This was a large service with multiple dependencies, serving both users and operators, making bug fixes particularly challenging. &lt;/p&gt;

&lt;p&gt;Let me share a recent experience where using a debugger saved me a significant amount of time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging in Kubernetes: A Real-World Example
&lt;/h3&gt;

&lt;p&gt;A few weeks ago, I was developing a feature that affected multiple parts of a microservice running in Kubernetes. This service was being called by several other components (like the app and admin panels), and I needed to trace what was happening inside the service, step by step. Tests couldn’t cover everything, especially when I needed to ensure that certain flags were set correctly to prevent sending multiple SMS notifications to users.&lt;/p&gt;

&lt;p&gt;I could have used logs to trace the service, but every time I missed an entry, I would have had to add a new one, push the code, and wait for the CI pipeline to complete—wasting 3-5 minutes each time just to deploy to staging.&lt;/p&gt;

&lt;p&gt;Instead, I decided to try using a debugger. Here’s how I set it up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up Delve Debugger in a Kubernetes Pod
&lt;/h3&gt;

&lt;p&gt;After a lot of trial and error, I found that adding the following lines to the Dockerfile allowed me to run Delve (a debugger for Go) inside the Kubernetes Pod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/bin/sh", "-c", "/go/bin/dlv --listen=127.0.0.1:8001 --headless=true --api-version=2 --only-same-user=false exec /path/to/exec"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To install Delve, I added this line to the Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/go-delve/delve/cmd/dlv@v1.23.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I compiled the Go application with specific flags to disable optimizations and inlining:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go build &lt;span class="nt"&gt;-gcflags&lt;/span&gt; &lt;span class="s2"&gt;"all=-N -l"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Connecting to the Debugger
&lt;/h3&gt;

&lt;p&gt;Once the service was deployed, I used port forwarding to connect to the debugger. I added the following port configuration to the Kubernetes manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dlv&lt;/span&gt;
    &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8081&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, I forwarded the port to my local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;oc port-forward &lt;span class="si"&gt;$(&lt;/span&gt;oc get pods &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;LABEL &lt;span class="nt"&gt;-o&lt;/span&gt; name | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 1&lt;span class="si"&gt;)&lt;/span&gt; 8001:8001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I could connect to the debugger from my local machine using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dlv connect :8001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setting Breakpoints and Configuring Paths
&lt;/h3&gt;

&lt;p&gt;With the debugger connected, I set breakpoints, such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;dlv&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;break &lt;/span&gt;main.main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the service was built in a different environment (GitLab’s pipeline), the paths didn’t match my local setup. To solve this, I used &lt;code&gt;substitute-path&lt;/code&gt; to map the paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;dlv&lt;span class="o"&gt;)&lt;/span&gt; config substitute-path /usr/local/go/ /Users/hossein/sdk/go1.23.1/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running these commands every time was tedious, so I created a script (&lt;code&gt;dlv.init&lt;/code&gt;) to automate the setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dlv connect :8001 &lt;span class="nt"&gt;--init&lt;/span&gt; dlv.init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation of Key Commands
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Delve Debugger Command:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dlv &lt;span class="nt"&gt;--listen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1:8001 &lt;span class="nt"&gt;--headless&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="nt"&gt;--api-version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nt"&gt;--only-same-user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false exec&lt;/span&gt; /path/to/exec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--listen&lt;/code&gt;: Opens the debugger on &lt;code&gt;127.0.0.1:8001&lt;/code&gt; for remote connections.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--headless=true&lt;/code&gt;: Runs Delve without an interactive UI, suitable for remote debugging.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--api-version=2&lt;/code&gt;: Uses API version 2 for better tool compatibility.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--only-same-user=false&lt;/code&gt;: Allows users other than the process owner to connect.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;exec /path/to/exec&lt;/code&gt;: Starts or attaches to the Go executable for debugging.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup allows remote debugging of a Go service in a Kubernetes environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Go Build Command:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go build &lt;span class="nt"&gt;-gcflags&lt;/span&gt; &lt;span class="s2"&gt;"all=-N -l"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-N&lt;/code&gt;: Disables optimizations.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-l&lt;/code&gt;: Prevents inlining of functions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These flags ensure the code stays closer to the source, making it easier to debug by allowing you to step through every function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Usage
&lt;/h3&gt;

&lt;p&gt;To demonstrate how to debug a remote service, I use a local Docker container for simplicity. However, in practice, this service would be deployed on Kubernetes.&lt;/p&gt;

&lt;p&gt;To build the Docker image, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will build a Docker image. Once built, you can start the service by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;make start
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8001:8001 &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="nt"&gt;--rm&lt;/span&gt; my-app
API server listening at: &lt;span class="o"&gt;[&lt;/span&gt;::]:8001
2024-10-12T13:48:31Z warning &lt;span class="nv"&gt;layer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rpc Listening &lt;span class="k"&gt;for &lt;/span&gt;remote connections &lt;span class="o"&gt;(&lt;/span&gt;connections are not authenticated nor encrypted&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prompt shows that the debugger is running. To attach the debugger to the remote service, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make debug
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;make debug
bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"dlv connect :8001 --init &amp;lt;(sed 's|PWD|'&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="s2"&gt;'|g; s|HOME|'&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;'|g' dlv.init)"&lt;/span&gt;
Type &lt;span class="s1"&gt;'help'&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;list of commands.
Breakpoint 1 &lt;span class="nb"&gt;set &lt;/span&gt;at 0x2188cc &lt;span class="k"&gt;for &lt;/span&gt;main.main.func1&lt;span class="o"&gt;()&lt;/span&gt; ./main.go:9
&lt;span class="o"&gt;(&lt;/span&gt;dlv&lt;span class="o"&gt;)&lt;/span&gt; c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once attached, send the &lt;code&gt;c&lt;/code&gt; command to continue execution. Then, send a request to the service using the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make send-request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The debugger will show output like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;Breakpoint 1] main.main.func1&lt;span class="o"&gt;()&lt;/span&gt; ./main.go:9 &lt;span class="o"&gt;(&lt;/span&gt;hits goroutine&lt;span class="o"&gt;(&lt;/span&gt;17&lt;span class="o"&gt;)&lt;/span&gt;:1 total:1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;PC: 0x2188cc&lt;span class="o"&gt;)&lt;/span&gt;
Warning: debugging optimized &lt;span class="k"&gt;function
&lt;/span&gt;Warning: listing may not match stale executable
     4:     &lt;span class="s2"&gt;"fmt"&lt;/span&gt;
     5:     &lt;span class="s2"&gt;"net/http"&lt;/span&gt;
     6: &lt;span class="o"&gt;)&lt;/span&gt;
     7:
     8: func main&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;   9:     http.HandleFunc&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;, func&lt;span class="o"&gt;(&lt;/span&gt;w http.ResponseWriter, r &lt;span class="k"&gt;*&lt;/span&gt;http.Request&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    10:         fmt.Fprintf&lt;span class="o"&gt;(&lt;/span&gt;w, &lt;span class="s2"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    11:     &lt;span class="o"&gt;})&lt;/span&gt;
    12:
    13:     http.ListenAndServe&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;":8080"&lt;/span&gt;, nil&lt;span class="o"&gt;)&lt;/span&gt;
    14: &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, you can manually debug the service. For example, to inspect the HTTP method, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;dlv&lt;span class="o"&gt;)&lt;/span&gt; p r.Method
&lt;span class="s2"&gt;"GET"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once troubleshooting is complete, use the &lt;code&gt;c&lt;/code&gt; command to continue and the client will receive the output.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Using a debugger in this case allowed me to step through the code and understand the service's behavior without constantly pushing new logging code. It was a game-changer, especially when working with complex microservices in Kubernetes. Debugging Go in production environments can be daunting, but with the right setup, it can save you a lot of time and frustration.&lt;/p&gt;

&lt;p&gt;You can check out the full example of this setup on &lt;a href="https://github.com/empire/go-dlv-k8s" rel="noopener noreferrer"&gt;my GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>dlv</category>
      <category>microservices</category>
    </item>
  </channel>
</rss>
