<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://amustaque97.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://amustaque97.github.io/" rel="alternate" type="text/html" hreflang="en-US" /><updated>2026-01-20T19:27:59+05:30</updated><id>https://amustaque97.github.io/feed.xml</id><title type="html">Mustaque Ahmed</title><subtitle>I use computers for fun and profit. In my spare time, I participate in the free and open-source software community.</subtitle><author><name>Mustaque Ahmed</name><email>amustaque97@gmail.com</email></author><entry><title type="html">How I Learned to Read EXPLAIN ANALYZE (and Stopped Guessing)</title><link href="https://amustaque97.github.io/how-to-read-analyze-command-postgresql/" rel="alternate" type="text/html" title="How I Learned to Read EXPLAIN ANALYZE (and Stopped Guessing)" /><published>2026-01-20T00:00:00+05:30</published><updated>2026-01-20T00:00:00+05:30</updated><id>https://amustaque97.github.io/how-to-read-analyze-command-postgresql</id><content type="html" xml:base="https://amustaque97.github.io/how-to-read-analyze-command-postgresql/"><![CDATA[<h1 id="how-i-learned-to-read-explain-analyze-and-stopped-guessing">How I Learned to Read <code class="language-plaintext highlighter-rouge">EXPLAIN ANALYZE</code> (and Stopped Guessing)</h1>

<p>For a long time, I treated slow SQL queries like a bad fever.<br />
I’d add an index, pray a little, deploy, and hope latency graphs went down.</p>

<p>Sometimes they did.<br />
Most times they didn’t.</p>

<p>This is the story of how one query — and one <code class="language-plaintext highlighter-rouge">EXPLAIN ANALYZE</code> output — changed how I debug databases forever.</p>

<hr />

<h2 id="the-incident">The Incident</h2>

<p>It was a normal weekday.<br />
Dashboards were green. Coffee was hot. Life was good.</p>

<p>Then a message dropped in Slack:</p>

<blockquote>
  <p>“Checkout page is slow. 3–5s. Users dropping.”</p>
</blockquote>

<p>The query looked innocent:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">u</span><span class="p">.</span><span class="n">id</span><span class="p">,</span> <span class="n">u</span><span class="p">.</span><span class="n">name</span><span class="p">,</span> <span class="k">SUM</span><span class="p">(</span><span class="n">o</span><span class="p">.</span><span class="n">amount</span><span class="p">)</span> <span class="k">AS</span> <span class="n">total_spent</span>
<span class="k">FROM</span> <span class="n">users</span> <span class="n">u</span>
<span class="k">JOIN</span> <span class="n">orders</span> <span class="n">o</span> <span class="k">ON</span> <span class="n">u</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">o</span><span class="p">.</span><span class="n">user_id</span>
<span class="k">WHERE</span> <span class="n">u</span><span class="p">.</span><span class="n">country</span> <span class="o">=</span> <span class="s1">'US'</span>
  <span class="k">AND</span> <span class="n">o</span><span class="p">.</span><span class="n">status</span> <span class="o">=</span> <span class="s1">'completed'</span>
  <span class="k">AND</span> <span class="n">o</span><span class="p">.</span><span class="n">created_at</span> <span class="o">&gt;</span> <span class="n">now</span><span class="p">()</span> <span class="o">-</span> <span class="n">interval</span> <span class="s1">'30 days'</span>
<span class="k">GROUP</span> <span class="k">BY</span> <span class="n">u</span><span class="p">.</span><span class="n">id</span><span class="p">,</span> <span class="n">u</span><span class="p">.</span><span class="n">name</span>
<span class="k">ORDER</span> <span class="k">BY</span> <span class="n">total_spent</span> <span class="k">DESC</span>
<span class="k">LIMIT</span> <span class="mi">5</span><span class="p">;</span>
</code></pre></div></div>

<p>It worked fine in staging.<br />
It worked fine last week.<br />
But production had grown — and the database was now angry.</p>

<hr />

<h2 id="stop-guessing-start-observing">Stop Guessing. Start Observing.</h2>

<p>I ran the one command I used to avoid:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="k">ANALYZE</span>
</code></pre></div></div>

<p>And this appeared:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Parallel Seq Scan on orders
(actual time=0.021..156.678 rows=28834 loops=2)
Rows Removed by Filter: 471166
</code></pre></div></div>

<p>That single line explained the entire slowdown.</p>

<p>PostgreSQL was scanning <strong>half a million rows</strong><br />
to find <strong>28k useful ones</strong>.</p>

<hr />

<h2 id="read-the-plan-like-a-story-bottom--top">Read the Plan Like a Story (Bottom → Top)</h2>

<p>Execution plans are stories, not walls of text.</p>

<ul>
  <li>The <strong>Seq Scan</strong> was the villain</li>
  <li>The <strong>Hash Join</strong> was doing its job</li>
  <li>The <strong>Aggregate</strong> was tired but necessary</li>
  <li>The <strong>Sort</strong> was actually optimized (top-N heapsort)</li>
</ul>

<p>The real problem was obvious:<br />
<strong>I wasn’t helping the planner.</strong></p>

<hr />

<h2 id="fix-the-root-cause-not-the-symptom">Fix the Root Cause, Not the Symptom</h2>

<p>I didn’t cache.<br />
I didn’t add replicas.<br />
I didn’t scale the database.</p>

<p>I added one intentional index:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">INDEX</span> <span class="n">idx_orders_completed_recent</span>
<span class="k">ON</span> <span class="n">orders</span> <span class="p">(</span><span class="n">created_at</span><span class="p">,</span> <span class="n">user_id</span><span class="p">)</span>
<span class="k">WHERE</span> <span class="n">status</span> <span class="o">=</span> <span class="s1">'completed'</span><span class="p">;</span>
</code></pre></div></div>

<p>A partial index.<br />
Small. Focused. Purpose-built.</p>

<hr />

<h2 id="rerun-explain-analyze-the-moment-of-truth">Rerun EXPLAIN ANALYZE (The Moment of Truth)</h2>

<p>The plan changed instantly:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Index Scan using idx_orders_completed_recent
(actual time=0.2..8ms)
</code></pre></div></div>

<p>Execution time dropped from:</p>

<blockquote>
  <p><strong>210ms → 71.25ms</strong></p>
</blockquote>

<p><img src="/assets/img/how-to-read-explain-command-postgresql/image1.png" alt="Supabase SQL editor" /></p>

<p>No infra changes.<br />
No rewrites.<br />
Just understanding.</p>

<hr />

<h2 id="the-lesson">The Lesson</h2>

<p>Before this, I believed:</p>

<blockquote>
  <p>“Slow queries need more resources.”</p>
</blockquote>

<p>Now I know:</p>

<blockquote>
  <p>“Slow queries need better plans.”</p>
</blockquote>

<p><code class="language-plaintext highlighter-rouge">EXPLAIN ANALYZE</code> isn’t just a debugging tool —<br />
it’s a conversation with your database.</p>

<hr />

<h2 id="final-thought">Final Thought</h2>

<p>The day I learned to read <code class="language-plaintext highlighter-rouge">EXPLAIN ANALYZE</code><br />
was the day I stopped being afraid of databases.</p>

<p>If you can read the plan,<br />
you can fix the problem.</p>

<p>Every time.</p>]]></content><author><name>Mustaque Ahmed</name><email>amustaque97@gmail.com</email></author><category term="postgresql" /><category term="sql" /><category term="performance" /><category term="databases" /><category term="engineering" /><category term="debugging" /><summary type="html"><![CDATA[A real-world story of debugging a slow PostgreSQL query using EXPLAIN ANALYZE — and how it changed the way I approach database performance.]]></summary></entry><entry><title type="html">The Tale of Automating Team Management</title><link href="https://amustaque97.github.io/automate-rust-team-management-repo/" rel="alternate" type="text/html" title="The Tale of Automating Team Management" /><published>2025-12-08T00:00:00+05:30</published><updated>2025-12-08T00:00:00+05:30</updated><id>https://amustaque97.github.io/automate-rust-team-management-repo</id><content type="html" xml:base="https://amustaque97.github.io/automate-rust-team-management-repo/"><![CDATA[<blockquote>
  <p><strong>Note</strong>: This automation infrastructure was developed and contributed in <a href="https://github.com/rust-lang/team/pull/2152">PR #2152</a>, adding comprehensive GitHub Actions support to enhance the team management workflow.</p>
</blockquote>

<h2 id="chapter-1-the-challenge">Chapter 1: The Challenge</h2>

<p>Once upon a time, in the bustling world of Rust development, there was a repository that held a crucial responsibility: managing teams, permissions, and configurations across multiple GitHub organizations. The <code class="language-plaintext highlighter-rouge">rust-lang/team</code> repository was the single source of truth for hundreds of developers, dozens of teams, and countless repositories spread across organizations like <code class="language-plaintext highlighter-rouge">rust-lang</code>, <code class="language-plaintext highlighter-rouge">rust-lang-nursery</code>, <code class="language-plaintext highlighter-rouge">rust-analyzer</code>, and more.</p>

<p>But with great power comes great responsibility—and great risk. Every change to this repository could affect real people’s access to real projects. A single mistake could lock someone out of a critical repository or accidentally grant excessive permissions. We needed a safety net, a way to preview changes before they went live.</p>

<h2 id="chapter-2-the-main-workflow---the-guardian">Chapter 2: The Main Workflow - The Guardian</h2>

<p>Enter <code class="language-plaintext highlighter-rouge">main.yml</code>, our primary guardian workflow. This workflow is triggered on every pull request, merge queue event, and runs on a schedule at 4 AM UTC daily to keep everything synchronized.</p>

<h3 id="the-test-phase">The Test Phase</h3>

<p>The journey begins with comprehensive testing:</p>

<ol>
  <li>
    <p><strong>Building the Fort</strong>: We compile the Rust code with zero tolerance for warnings (<code class="language-plaintext highlighter-rouge">RUSTFLAGS="--deny warnings"</code>), ensuring code quality from the ground up.</p>
  </li>
  <li>
    <p><strong>Validation Gauntlet</strong>: The repository contents undergo strict validation (<code class="language-plaintext highlighter-rouge">cargo run -- check --strict</code>), verifying that all team definitions, repository configurations, and permissions are valid before proceeding.</p>
  </li>
  <li><strong>Code Quality Checks</strong>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">rustfmt</code> ensures consistent formatting</li>
      <li><code class="language-plaintext highlighter-rouge">clippy</code> catches potential bugs and anti-patterns</li>
      <li>A full test suite runs to verify functionality</li>
    </ul>
  </li>
  <li>
    <p><strong>CODEOWNERS Verification</strong>: We check that the CODEOWNERS file is up-to-date, ensuring proper review requirements are in place.</p>
  </li>
  <li><strong>Static API Generation</strong>: The workflow builds a static API containing all team data as JSON files, which gets uploaded as an artifact for later use.</li>
</ol>

<h3 id="the-deploy-phase">The Deploy Phase</h3>

<p>But the real magic happens in the deployment phase—only when everything passes and we’re not in a pull request:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">environment</span><span class="pi">:</span> <span class="s">deploy</span>
</code></pre></div></div>

<p>This phase is protected. It only runs on the main branch, and it wields powerful secrets:</p>

<ul>
  <li><strong>Multiple GitHub Tokens</strong>: Organization-specific tokens for <code class="language-plaintext highlighter-rouge">rust-lang</code>, <code class="language-plaintext highlighter-rouge">rust-lang-nursery</code>, <code class="language-plaintext highlighter-rouge">bors-rs</code>, and others</li>
  <li><strong>Mailgun API Token</strong>: For managing mailing lists</li>
  <li><strong>Zulip Credentials</strong>: For synchronizing team chat access</li>
  <li><strong>Crates.io Token</strong>: For managing crate publishing permissions</li>
  <li><strong>Email Encryption Key</strong>: For securely handling encrypted email addresses</li>
</ul>

<p>The crown jewel? The <code class="language-plaintext highlighter-rouge">sync apply</code> command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo run <span class="nb">sync </span>apply <span class="nt">--src</span> build
</code></pre></div></div>

<p>This single command orchestrates a symphony of API calls, synchronizing:</p>
<ul>
  <li>GitHub team memberships and permissions</li>
  <li>Repository access controls</li>
  <li>Branch protection rules</li>
  <li>Environment configurations</li>
  <li>Mailing list subscriptions</li>
  <li>Zulip stream access</li>
</ul>

<p>Finally, the built static API is deployed to GitHub Pages, making team data accessible at <code class="language-plaintext highlighter-rouge">team-api.infra.rust-lang.org</code>.</p>

<h2 id="chapter-3-the-dry-run---the-crystal-ball">Chapter 3: The Dry Run - The Crystal Ball</h2>

<p>But how do we know what will happen before we merge? This is where <code class="language-plaintext highlighter-rouge">dry-run.yml</code> comes in—our crystal ball that peers into the future.</p>

<p>This workflow employs a clever security pattern using <code class="language-plaintext highlighter-rouge">workflow_run</code>:</p>

<ol>
  <li>
    <p><strong>Trigger</strong>: It activates after the main CI workflow completes successfully on a pull request.</p>
  </li>
  <li><strong>Security First</strong>:
    <ul>
      <li>For PRs from forks, it checks out the <code class="language-plaintext highlighter-rouge">main</code> branch code (never the untrusted PR code)</li>
      <li>For PRs from the main repository, it can safely use the PR’s code</li>
      <li>This prevents malicious code from running with elevated permissions</li>
    </ul>
  </li>
  <li>
    <p><strong>The Magic Tokens</strong>: Using a custom composite action, it generates organization-scoped GitHub App tokens for each organization we manage. This is more secure than using a single PAT and provides better auditing.</p>
  </li>
  <li><strong>Preview the Future</strong>: It runs <code class="language-plaintext highlighter-rouge">sync print-plan</code> to show exactly what changes would be applied:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./target/release/rust-team <span class="nb">sync </span>print-plan <span class="se">\</span>
  <span class="nt">--services</span> crates-io,github <span class="se">\</span>
  <span class="nt">--src</span> team-api
</code></pre></div>    </div>
  </li>
  <li><strong>Feedback Loop</strong>: The results are posted as a comment on the PR, showing reviewers:
    <ul>
      <li>Which teams will be created, updated, or deleted</li>
      <li>Permission changes for repositories</li>
      <li>Branch protection modifications</li>
      <li>Environment updates (now with detailed branch additions/removals!)</li>
    </ul>
  </li>
</ol>

<p>The comment is idempotent—it edits the last comment if one exists, keeping the PR clean and up-to-date.</p>

<h2 id="chapter-4-the-supporting-cast---reusable-actions">Chapter 4: The Supporting Cast - Reusable Actions</h2>

<h3 id="the-setup-rust-action">The Setup Rust Action</h3>

<p>Our <code class="language-plaintext highlighter-rouge">setup-rust</code> composite action is the foundation, ensuring we always have the right Rust toolchain and leveraging caching to speed up builds:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install Rust Stable</span>
  <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
    <span class="s">rustup update stable</span>
    <span class="s">rustup default stable</span>
</code></pre></div></div>

<h3 id="the-token-generator-action">The Token Generator Action</h3>

<p>The <code class="language-plaintext highlighter-rouge">generate-tokens</code> action is a masterstroke of security architecture. Instead of using a single, all-powerful token, it generates organization-specific tokens from a GitHub App:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">outputs</span><span class="pi">:</span>
  <span class="na">rust-lang-token</span><span class="pi">:</span> <span class="s">...</span>
  <span class="na">rust-lang-deprecated-token</span><span class="pi">:</span> <span class="s">...</span>
  <span class="na">rust-lang-nursery-token</span><span class="pi">:</span> <span class="s">...</span>
  <span class="c1"># ... and more</span>
</code></pre></div></div>

<p>Each organization gets its own scoped token, providing:</p>
<ul>
  <li><strong>Principle of Least Privilege</strong>: Each token can only access its organization</li>
  <li><strong>Better Auditing</strong>: Actions are attributed to the GitHub App</li>
  <li><strong>Easier Rotation</strong>: Tokens expire automatically</li>
</ul>

<h2 id="chapter-5-the-safeguards---codeowners">Chapter 5: The Safeguards - CODEOWNERS</h2>

<p>The <code class="language-plaintext highlighter-rouge">.github/CODEOWNERS</code> file adds an extra layer of protection. It’s automatically generated and enforces that:</p>

<ul>
  <li>Most data files (people, teams, repos TOML files) can be approved by any maintainer with write access</li>
  <li>Critical files require admin approval:
    <ul>
      <li>The team repository configuration itself</li>
      <li>Admin team definitions</li>
      <li>Individual admin user files</li>
    </ul>
  </li>
</ul>

<p>This creates a trust boundary: while the community can propose changes to most team configurations, changes that could affect the security or stability of the system require multiple sets of eyes.</p>

<h2 id="chapter-6-the-concurrency-dance">Chapter 6: The Concurrency Dance</h2>

<p>One of the most elegant aspects is how concurrency is managed:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">concurrency</span><span class="pi">:</span>
  <span class="na">group</span><span class="pi">:</span> <span class="s">$-$</span>
  <span class="na">cancel-in-progress</span><span class="pi">:</span> <span class="no">false</span>
</code></pre></div></div>

<p>This ensures:</p>
<ul>
  <li>PRs can run tests in parallel (each has a unique <code class="language-plaintext highlighter-rouge">head_ref</code>)</li>
  <li>Deployment never runs in parallel (uses constant ‘deploy’ string)</li>
  <li>The cron job, merge queue, and manual triggers share the same deployment slot</li>
</ul>

<p>It’s like a sophisticated traffic control system, allowing maximum parallelism for testing while guaranteeing serialization for critical deployment operations.</p>

<h2 id="epilogue-the-living-system">Epilogue: The Living System</h2>

<p>This GitHub Actions setup isn’t just code—it’s a living system that embodies trust, security, and automation. It allows the Rust community to self-manage their team structure while maintaining guardrails against mistakes. It provides transparency through dry-run previews and enforces security through scoped permissions and code review requirements.</p>

<p>Every day at 4 AM UTC, it quietly ensures that the desired state matches reality. Every pull request triggers a preview of changes. Every merge safely applies those changes across multiple platforms.</p>

<p>It’s infrastructure as code at its finest: declarative, reviewable, and automated. And it all starts with a simple TOML file describing who belongs to which team.</p>

<h2 id="about-this-work">About This Work</h2>

<p>This automation infrastructure was built from the ground up to bring modern CI/CD practices to team management. The work involved:</p>

<ul>
  <li><strong>Designing the dual-workflow pattern</strong>: Separating testing from deployment while maintaining security</li>
  <li><strong>Implementing the dry-run preview system</strong>: Using <code class="language-plaintext highlighter-rouge">workflow_run</code> triggers to safely preview changes from untrusted forks</li>
  <li><strong>Creating reusable composite actions</strong>: Building modular components for Rust setup and token generation</li>
  <li><strong>Establishing the token architecture</strong>: Moving from single PATs to organization-scoped GitHub App tokens for better security and auditing</li>
  <li><strong>Orchestrating the deployment pipeline</strong>: Connecting team data validation, static API generation, and multi-service synchronization</li>
</ul>

<p>The complete implementation can be found in <a href="https://github.com/rust-lang/team/pull/2152">PR #2152</a>, which introduced these GitHub Actions workflows and supporting infrastructure to the rust-lang/team repository.</p>

<hr />

<p><em>This automation enables the Rust project to scale its governance and team management without bottlenecking on manual processes. What once required careful manual API calls now happens automatically, safely, and transparently.</em></p>]]></content><author><name>Mustaque Ahmed</name><email>amustaque97@gmail.com</email></author><category term="infra" /><category term="rust" /><category term="actions" /><category term="pipelines" /><category term="github" /><summary type="html"><![CDATA[Want to know comprehensive GitHub Actions support to enhance the team management workflow.]]></summary></entry><entry><title type="html">The Mystery of the Missing Data</title><link href="https://amustaque97.github.io/recording-vs-non-recording-spans/" rel="alternate" type="text/html" title="The Mystery of the Missing Data" /><published>2025-11-26T00:00:00+05:30</published><updated>2025-11-26T00:00:00+05:30</updated><id>https://amustaque97.github.io/recording-vs-non-recording-spans</id><content type="html" xml:base="https://amustaque97.github.io/recording-vs-non-recording-spans/"><![CDATA[<p>It was 2 AM on a Tuesday when I got the call. Our production microservices were experiencing mysterious performance degradation, and the observability team was baffled. Traces were showing up for some requests but not others, and when they did appear, they were incomplete. The worst part? The CPU usage was through the roof.</p>

<p>I had been working with OpenTelemetry for about six months at that point, integrating it across our Python microservices architecture. We run a platform processing millions of requests daily, so every millisecond matters. That night, I learned a lesson that fundamentally changed how I think about distributed tracing.</p>

<h3 id="the-setup-what-we-were-doing">The Setup: What We Were Doing</h3>

<p>Our system had three main services:</p>

<ol>
  <li><strong>API Gateway</strong> - Entry point for all requests</li>
  <li><strong>Auth Service</strong> - Validates user credentials</li>
  <li><strong>Data Service</strong> - Processes and stores data</li>
</ol>

<p>We had configured OpenTelemetry with what I thought was a sensible setup:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">ProbabilitySampler</code> set to 10% on the API Gateway (sample 1 in 10 requests)</li>
  <li><code class="language-plaintext highlighter-rouge">AlwaysOnSampler</code> on the Auth and Data services (sample everything)</li>
</ul>

<p>The reasoning seemed sound: we don’t want to process every trace, so sample at the entry point and let downstream services decide independently. Simple, right? Wrong.</p>

<h3 id="the-problem-reveals-itself">The Problem Reveals Itself</h3>

<p>I pulled up the Jaeger dashboard and noticed something odd. When the API Gateway sampled a request (the 10% that passed through), the downstream services were creating spans, but they were being created as <strong>NonRecording spans</strong>. These spans had zero effect—they didn’t show up in our traces, they didn’t export data, and they didn’t contribute to the trace we wanted to analyze.</p>

<p>But here’s the catch: these NonRecording spans were still doing <em>something</em>. Each one was still being created, still propagating context, still going through span processors. It felt like we were paying a cost for spans we weren’t even using.</p>

<p>I started digging into the OpenTelemetry specification and the Python SDK source code. That’s when I discovered the distinction that would change everything.</p>

<h3 id="recording-spans-vs-nonrecording-spans-the-epiphany">Recording Spans vs. NonRecording Spans: The Epiphany</h3>

<p><strong>Recording Spans</strong> are the spans you actually want. They:</p>
<ul>
  <li>Capture all your <code class="language-plaintext highlighter-rouge">SetAttribute()</code> calls, <code class="language-plaintext highlighter-rouge">AddEvent()</code> calls, and status information</li>
  <li>Get processed by span processors and eventually exported to Jaeger, Datadog, or wherever</li>
  <li>Have a meaningful overhead because they’re storing real data</li>
  <li>Show up in your traces and dashboards</li>
</ul>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># A Recording Span - everything gets captured
</span><span class="k">with</span> <span class="n">tracer</span><span class="p">.</span><span class="n">start_as_current_span</span><span class="p">(</span><span class="s">"database_query"</span><span class="p">)</span> <span class="k">as</span> <span class="n">span</span><span class="p">:</span>
    <span class="n">span</span><span class="p">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s">"query_type"</span><span class="p">,</span> <span class="s">"SELECT"</span><span class="p">)</span>
    <span class="n">span</span><span class="p">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s">"table"</span><span class="p">,</span> <span class="s">"users"</span><span class="p">)</span>
    <span class="n">span</span><span class="p">.</span><span class="n">add_event</span><span class="p">(</span><span class="s">"query_started"</span><span class="p">)</span>
    <span class="c1"># ... do work ...
</span>    <span class="n">span</span><span class="p">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s">"rows_returned"</span><span class="p">,</span> <span class="mi">42</span><span class="p">)</span>
</code></pre></div></div>

<p><strong>NonRecording Spans</strong>, on the other hand, are the spans that don’t make the cut:</p>
<ul>
  <li>The sampler decided they shouldn’t be recorded</li>
  <li>All calls to <code class="language-plaintext highlighter-rouge">SetAttribute()</code>, <code class="language-plaintext highlighter-rouge">AddEvent()</code>, etc. are instant no-ops</li>
  <li>They’re never exported anywhere</li>
  <li>They still propagate trace context to child services (crucial for distributed tracing)</li>
  <li>They should be virtually free from a performance perspective</li>
</ul>

<p>But here’s where I got it wrong: I was treating NonRecording spans like they cost nothing, when in reality, I was <em>creating a NonRecording span for every single request</em> even though the parent didn’t get recorded.</p>

<h3 id="the-ah-ha-moment">The Ah-Ha Moment</h3>

<p>The issue was in how I’d configured the samplers. The API Gateway sampled at 10%, which meant 90% of requests got NonRecording spans. Those NonRecording spans then propagated the “don’t record this” decision downstream through the trace context headers.</p>

<p>When the Auth Service received a request without the “record” flag, even though it was configured with <code class="language-plaintext highlighter-rouge">AlwaysOnSampler</code>, it respected the parent’s decision (thanks to <code class="language-plaintext highlighter-rouge">ParentBasedSampler</code> being the default behavior). So it created a NonRecording span too.</p>

<p>The cascade continued to the Data Service. Same result.</p>

<p>So here’s what was happening:</p>
<ol>
  <li>Request arrives at API Gateway</li>
  <li>90% of the time: NonRecording span created (no-op)</li>
  <li>Request goes to Auth Service</li>
  <li>Auth Service sees “don’t record this trace” in the context</li>
  <li>NonRecording span created (no-op)</li>
  <li>Request goes to Data Service</li>
  <li>Same story: NonRecording span created</li>
</ol>

<p>Three NonRecording spans per request, 90% of the time. Millions of requests per day. That’s a lot of no-ops.</p>

<p>But wait—NonRecording spans are supposed to be free. So why was CPU spiking?</p>

<h3 id="the-real-culprit-premature-optimization-gone-wrong">The Real Culprit: Premature Optimization Gone Wrong</h3>

<p>Then I looked at my span processor code. That was the problem. I had written a custom span processor that was doing <em>everything</em> unconditionally:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">MyCustomSpanProcessor</span><span class="p">(</span><span class="n">SpanProcessor</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">on_start</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">span</span><span class="p">,</span> <span class="n">parent_context</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="c1"># I was doing expensive operations on EVERY span
</span>        <span class="n">detailed_info</span> <span class="o">=</span> <span class="n">get_detailed_system_info</span><span class="p">()</span>  <span class="c1"># OUCH!
</span>        <span class="n">user_context</span> <span class="o">=</span> <span class="n">lookup_user_from_database</span><span class="p">()</span>   <span class="c1"># OUCH!
</span>        <span class="n">span</span><span class="p">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s">"system_info"</span><span class="p">,</span> <span class="n">detailed_info</span><span class="p">)</span>
        <span class="n">span</span><span class="p">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s">"user"</span><span class="p">,</span> <span class="n">user_context</span><span class="p">)</span>
    
    <span class="k">def</span> <span class="nf">on_end</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">span</span><span class="p">):</span>
        <span class="c1"># More expensive work
</span>        <span class="k">pass</span>
</code></pre></div></div>

<p>The problem: this processor was running on NonRecording spans too! Even though the span would never be exported, my code was computing expensive information for every single one.</p>

<p>It took me a few hours of profiling to realize that most of the CPU cost was coming from this processor, not from the span creation itself.</p>

<h3 id="the-fix-one-simple-check">The Fix: One Simple Check</h3>

<p>The fix was almost embarrassingly simple. I just needed to check if the span was recording before doing expensive work:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">MyCustomSpanProcessor</span><span class="p">(</span><span class="n">SpanProcessor</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">on_start</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">span</span><span class="p">,</span> <span class="n">parent_context</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="c1"># Only do expensive operations if we're actually recording
</span>        <span class="k">if</span> <span class="n">span</span><span class="p">.</span><span class="n">is_recording</span><span class="p">():</span>
            <span class="n">detailed_info</span> <span class="o">=</span> <span class="n">get_detailed_system_info</span><span class="p">()</span>
            <span class="n">user_context</span> <span class="o">=</span> <span class="n">lookup_user_from_database</span><span class="p">()</span>
            <span class="n">span</span><span class="p">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s">"system_info"</span><span class="p">,</span> <span class="n">detailed_info</span><span class="p">)</span>
            <span class="n">span</span><span class="p">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s">"user"</span><span class="p">,</span> <span class="n">user_context</span><span class="p">)</span>
    
    <span class="k">def</span> <span class="nf">on_end</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">span</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">span</span><span class="p">.</span><span class="n">is_recording</span><span class="p">():</span>
            <span class="c1"># Only process spans that will actually be exported
</span>            <span class="k">pass</span>
</code></pre></div></div>

<p>But there’s more. I also added a guard in my instrumented code:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">"/api/users/&lt;user_id&gt;"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">get_user</span><span class="p">(</span><span class="n">user_id</span><span class="p">):</span>
    <span class="k">with</span> <span class="n">tracer</span><span class="p">.</span><span class="n">start_as_current_span</span><span class="p">(</span><span class="s">"fetch_user_details"</span><span class="p">)</span> <span class="k">as</span> <span class="n">span</span><span class="p">:</span>
        <span class="c1"># Only compute expensive data if we're recording
</span>        <span class="k">if</span> <span class="n">span</span><span class="p">.</span><span class="n">is_recording</span><span class="p">():</span>
            <span class="n">user_data</span> <span class="o">=</span> <span class="n">fetch_from_database</span><span class="p">(</span><span class="n">user_id</span><span class="p">)</span>
            <span class="n">span</span><span class="p">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s">"user_data"</span><span class="p">,</span> <span class="n">serialize</span><span class="p">(</span><span class="n">user_data</span><span class="p">))</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="c1"># Still fetch data for the response, just don't add to span
</span>            <span class="n">user_data</span> <span class="o">=</span> <span class="n">fetch_from_database</span><span class="p">(</span><span class="n">user_id</span><span class="p">)</span>
        
        <span class="k">return</span> <span class="n">user_data</span>
</code></pre></div></div>

<p>I deployed this fix on a Tuesday afternoon. By Wednesday morning, CPU usage had dropped by 35%. Let me repeat that: <strong>35% CPU reduction</strong> just by adding a single <code class="language-plaintext highlighter-rouge">if span.is_recording()</code> check.</p>

<h3 id="what-changed">What Changed</h3>

<p>Here’s what I learned that night (well, morning… it was a long debugging session):</p>

<ol>
  <li>
    <p><strong>NonRecording spans are truly free if you treat them correctly</strong> - If you don’t do anything in your code that requires actually recording data, a NonRecording span has virtually zero overhead. The span context propagation is handled at the framework level.</p>
  </li>
  <li>
    <p><strong>The sampler makes the decision at span creation time</strong> - You don’t get to decide later whether a span is recording. The sampler decides when the span is created based on your sampling strategy and parent context.</p>
  </li>
  <li>
    <p><strong>Sampling strategy matters downstream</strong> - Using <code class="language-plaintext highlighter-rouge">ParentBasedSampler</code> (the default) means downstream services respect the parent’s sampling decision. This ensures consistency but also means a single sampling decision at your entry point affects the entire trace tree.</p>
  </li>
  <li>
    <p><strong>Check before expensive operations</strong> - Always guard expensive operations with <code class="language-plaintext highlighter-rouge">span.is_recording()</code>. This is a pattern I now follow religiously.</p>
  </li>
  <li>
    <p><strong>NonRecording spans still propagate context</strong> - Even though they’re not recorded, they still pass trace context to child services. This is essential for distributed tracing to work correctly across your system.</p>
  </li>
</ol>

<h3 id="the-architecture-lesson">The Architecture Lesson</h3>

<p>Looking back, I realized my sampling strategy was actually the real issue. I was trying to do sampling at the entry point, but I hadn’t considered the implications downstream.</p>

<p>Here’s what I changed:</p>

<p><strong>Before (naive approach):</strong></p>
<ul>
  <li>API Gateway: 10% sample rate</li>
  <li>Auth Service: Always sample (but respects parent)</li>
  <li>Data Service: Always sample (but respects parent)</li>
  <li>Result: 90% of traces were silently not recorded, leading to suspicious NonRecording spans everywhere</li>
</ul>

<p><strong>After (thoughtful approach):</strong></p>
<ul>
  <li>API Gateway: 100% sample rate (it’s the entry point; we make the decision here)</li>
  <li>Auth Service: Respect parent (no sampler specified, uses default ParentBasedSampler)</li>
  <li>Data Service: Respect parent (same as above)</li>
  <li>Result: Clear recording decisions, no unnecessary NonRecording spans, cleaner mental model</li>
</ul>

<p>OR, if we really wanted 10% sampling:</p>

<ul>
  <li>API Gateway: 10% sample rate</li>
  <li>Auth Service: 10% sample rate (not respect parent, be explicit)</li>
  <li>Data Service: 10% sample rate (not respect parent, be explicit)</li>
  <li>Result: Consistent sampling across all services, no surprises from NonRecording spans</li>
</ul>

<h3 id="the-lesson-sticks">The Lesson Sticks</h3>

<p>That incident taught me that OpenTelemetry’s design, while powerful, requires you to understand the underlying concepts deeply. NonRecording spans aren’t a flaw—they’re a feature. They allow efficient sampling by creating no-op spans that still propagate context. But you need to know how to work with them.</p>

<p>Now, whenever I instrument code, I follow these principles:</p>

<ol>
  <li><strong>Understand your sampler</strong> - Know why spans are being sampled the way they are</li>
  <li><strong>Use <code class="language-plaintext highlighter-rouge">is_recording()</code> as a guard</strong> - Before expensive operations, check if the span is recording</li>
  <li><strong>Configure samplers consciously</strong> - Think about where you want sampling decisions made</li>
  <li><strong>Profile before and after</strong> - Don’t assume anything; measure the impact</li>
</ol>

<h3 id="code-example-the-right-way">Code Example: The Right Way</h3>

<p>Here’s the pattern I now use in all my instrumented code:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">opentelemetry</span> <span class="kn">import</span> <span class="n">trace</span>

<span class="n">tracer</span> <span class="o">=</span> <span class="n">trace</span><span class="p">.</span><span class="n">get_tracer</span><span class="p">(</span><span class="n">__name__</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">process_request</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
    <span class="k">with</span> <span class="n">tracer</span><span class="p">.</span><span class="n">start_as_current_span</span><span class="p">(</span><span class="s">"process_request"</span><span class="p">)</span> <span class="k">as</span> <span class="n">span</span><span class="p">:</span>
        <span class="c1"># Cheap operations - always do these
</span>        <span class="n">span</span><span class="p">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s">"request_id"</span><span class="p">,</span> <span class="n">request</span><span class="p">.</span><span class="nb">id</span><span class="p">)</span>
        <span class="n">span</span><span class="p">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s">"method"</span><span class="p">,</span> <span class="n">request</span><span class="p">.</span><span class="n">method</span><span class="p">)</span>
        
        <span class="c1"># Expensive operations - guard with is_recording()
</span>        <span class="k">if</span> <span class="n">span</span><span class="p">.</span><span class="n">is_recording</span><span class="p">():</span>
            <span class="n">span</span><span class="p">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s">"request_headers"</span><span class="p">,</span> <span class="nb">dict</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="n">headers</span><span class="p">))</span>
            <span class="n">span</span><span class="p">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s">"user_context"</span><span class="p">,</span> <span class="n">get_user_context</span><span class="p">(</span><span class="n">request</span><span class="p">))</span>
        
        <span class="c1"># Your business logic
</span>        <span class="n">result</span> <span class="o">=</span> <span class="n">do_work</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
        
        <span class="c1"># Record result only if we're sampling
</span>        <span class="k">if</span> <span class="n">span</span><span class="p">.</span><span class="n">is_recording</span><span class="p">():</span>
            <span class="n">span</span><span class="p">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s">"result_status"</span><span class="p">,</span> <span class="n">result</span><span class="p">.</span><span class="n">status</span><span class="p">)</span>
            <span class="n">span</span><span class="p">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s">"processing_time_ms"</span><span class="p">,</span> <span class="n">result</span><span class="p">.</span><span class="n">time_ms</span><span class="p">)</span>
        
        <span class="k">return</span> <span class="n">result</span>
</code></pre></div></div>

<h3 id="conclusion">Conclusion</h3>

<p>OpenTelemetry’s distinction between recording and non-recording spans reflects a deep understanding of observability at scale. It’s not enough to just add tracing to your code; you need to understand <em>how</em> tracing decisions cascade through your system.</p>

<p>That 2 AM incident, the mysterious CPU spike, and the hours spent debugging—they all led me to appreciate the elegance of this design. NonRecording spans let you sample efficiently without sacrificing distributed trace context propagation. But you have to use them wisely.</p>

<p>If you’re working with OpenTelemetry in Python, I’d recommend taking the time to understand recording vs. non-recording spans deeply. Add some profiling to see where the overhead is coming from. And always, <em>always</em> check <code class="language-plaintext highlighter-rouge">span.is_recording()</code> before doing expensive work.</p>

<p>Your CPU (and your SRE team) will thank you.</p>]]></content><author><name>Mustaque Ahmed</name><email>amustaque97@gmail.com</email></author><category term="python" /><category term="observability" /><category term="programming" /><summary type="html"><![CDATA[A Journey Through OpenTelemetry Performance]]></summary></entry><entry><title type="html">AWS Releases ECS Express</title><link href="https://amustaque97.github.io/aws-ecs-express/" rel="alternate" type="text/html" title="AWS Releases ECS Express" /><published>2025-11-24T00:00:00+05:30</published><updated>2025-11-24T00:00:00+05:30</updated><id>https://amustaque97.github.io/aws-ecs-express</id><content type="html" xml:base="https://amustaque97.github.io/aws-ecs-express/"><![CDATA[<p>AWS released AWS ECS Express recently, a couple of days ago, a new capability from Amazon Elastic Container Service (Amazon ECS) that helps you launch highly available, scalable containerized applications with a single command. ECS Express Mode automates infrastructure setup including domains, networking, load balancing, and auto scaling through simplified APIs. This means you can focus on building applications while deploying with confidence using Amazon Web Services (AWS) best practices. Furthermore, when your applications evolve and require advanced features, you can seamlessly configure and access the full capabilities of the resources, including Amazon ECS.</p>

<p>This new offering is aimed at teams who want to run containerized applications without wiring up the full set of ECS primitives themselves. Express handles provisioning of Application Load Balancers, target groups, security groups, task definitions, and autoscaling policies for you, and performs rolling (zero-downtime) updates when service configuration changes.</p>

<h2 id="why-this-matters">Why this matters</h2>

<ul>
  <li>Faster onboarding: developers can describe a single <code class="language-plaintext highlighter-rouge">primary_container</code> and minimal configuration, and the service provisions the rest. No hand-rolling ALBs and target groups for each app.</li>
  <li>Zero-downtime updates: built-in rolling deployments mean safer configuration changes and smoother releases.</li>
  <li>Built-in best-practices: useful defaults for health checks, logging, and autoscaling remove a lot of boilerplate and potential misconfiguration.</li>
</ul>

<h2 id="key-features">Key features</h2>

<ul>
  <li>Declarative service provisioning that creates an ECS service, task definition, ALB (with ingress), target groups, and autoscaling.</li>
  <li>Support for container logging (CloudWatch), environment variables, secrets from Secrets Manager / SSM, and custom health check paths.</li>
  <li>Network configuration for <code class="language-plaintext highlighter-rouge">awsvpc</code> tasks with subnets and security groups.</li>
  <li>Autoscaling based on CPU or memory with configurable min/max task counts.</li>
  <li><code class="language-plaintext highlighter-rouge">wait_for_steady_state</code> and configurable timeouts for create/update/delete operations.</li>
</ul>

<h2 id="quick-terraform-example">Quick Terraform example</h2>

<p>Here is a minimal example showing how to use the resource (note the <code class="language-plaintext highlighter-rouge">time_sleep</code> caveat below):</p>

<div class="language-terraform highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">resource</span> <span class="s2">"time_sleep"</span> <span class="s2">"wait_for_iam"</span> <span class="p">{</span>
  <span class="nx">depends_on</span>      <span class="p">=</span> <span class="p">[</span><span class="nx">aws_iam_role_policy_attachment</span><span class="p">.</span><span class="nx">infrastructure</span><span class="p">]</span>
  <span class="nx">create_duration</span> <span class="p">=</span> <span class="s2">"7s"</span>
<span class="p">}</span>

<span class="k">resource</span> <span class="s2">"aws_ecs_express_gateway_service"</span> <span class="s2">"example"</span> <span class="p">{</span>
  <span class="nx">execution_role_arn</span>       <span class="p">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">execution</span><span class="p">.</span><span class="nx">arn</span>
  <span class="nx">infrastructure_role_arn</span>  <span class="p">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">infrastructure</span><span class="p">.</span><span class="nx">arn</span>

  <span class="nx">primary_container</span> <span class="p">{</span>
    <span class="nx">image</span>          <span class="p">=</span> <span class="s2">"nginx:stable"</span>
    <span class="nx">container_port</span> <span class="p">=</span> <span class="mi">80</span>
  <span class="p">}</span>

  <span class="nx">depends_on</span> <span class="p">=</span> <span class="p">[</span><span class="nx">time_sleep</span><span class="p">.</span><span class="nx">wait_for_iam</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="iam-role-timing-important">IAM Role Timing (important)</h2>

<p>If you create IAM roles and then immediately reference them in the same Terraform apply, AWS eventual consistency can cause failures (the new role may not be fully propagated). To reduce failures:</p>

<ul>
  <li>Add a small <code class="language-plaintext highlighter-rouge">time_sleep</code> resource (example above) and make your Express service depend on it; or</li>
  <li>Split your changes into two <code class="language-plaintext highlighter-rouge">apply</code> steps (create roles first, then services); or</li>
  <li>Implement an explicit polling/wait mechanism for role propagation in your automation.</li>
</ul>

<p>A short 5–10 second wait typically resolves propagation issues in CI systems.</p>

<h2 id="when-to-use-express-vs-full-ecs-control">When to use Express vs full ECS control</h2>

<p>Use Express when:</p>
<ul>
  <li>You want a fast, low-friction way to run a single container web service with sensible defaults.</li>
  <li>You prefer managing high-level service configuration and letting AWS handle the infra details.</li>
</ul>

<p>Prefer full ECS (manual task definitions, service, ALB, target groups) when:</p>
<ul>
  <li>You need complex multi-container task definitions, sidecars, or very custom networking/security requirements.</li>
  <li>You need fine-grained control over every part of the deployment stack.</li>
</ul>

<p>Blog link: https://aws.amazon.com/blogs/aws/build-production-ready-applications-without-infrastructure-complexity-using-amazon-ecs-express-mode/</p>]]></content><author><name>Mustaque Ahmed</name><email>amustaque97@gmail.com</email></author><category term="aws" /><category term="containers" /><category term="devops" /><summary type="html"><![CDATA[a simpler way to run containerized web services]]></summary></entry><entry><title type="html">Setting Up VCS Integration in Appwrite</title><link href="https://amustaque97.github.io/setup-appwrite-github-vcs/" rel="alternate" type="text/html" title="Setting Up VCS Integration in Appwrite" /><published>2025-11-17T00:00:00+05:30</published><updated>2025-11-17T00:00:00+05:30</updated><id>https://amustaque97.github.io/setup-appwrite-github-vcs</id><content type="html" xml:base="https://amustaque97.github.io/setup-appwrite-github-vcs/"><![CDATA[<p>If you’ve ever wished your <strong>local Appwrite environment</strong> could automatically deploy your functions or web code on every <code class="language-plaintext highlighter-rouge">git push</code>, this guide is for you. Setting up Version Control System (VCS) integration might sound intimidating, but once you walk through it step-by-step, it feels surprisingly straightforward.</p>

<p>In this short post, I’ll walk you through how to set up GitHub VCS integration for a local Appwrite instance — based on my own notes while configuring it.</p>

<h2 id="-what-you-need-before-starting">🧰 What You Need Before Starting</h2>

<ul>
  <li>A local Appwrite instance running via Docker Compose</li>
  <li>A GitHub account</li>
  <li>Basic understanding of Git</li>
</ul>

<p>That’s it.</p>

<h2 id="1-create-a-github-app">1. Create a GitHub App</h2>

<p>To let Appwrite talk to GitHub, you first need to create a <em>GitHub App</em>.</p>

<p>Head over to:<br />
<strong>GitHub → Settings → Developer Settings → GitHub Apps → New GitHub App</strong></p>

<p>Fill in the basics:</p>

<ul>
  <li><strong>Name:</strong> Anything you like, e.g., <em>Appwrite Local Dev</em></li>
  <li><strong>Homepage URL:</strong> <code class="language-plaintext highlighter-rouge">http://localhost</code></li>
  <li><strong>Callback URL:</strong><br />
<code class="language-plaintext highlighter-rouge">http://localhost/v1/vcs/github/callback</code></li>
  <li>(Optional) <strong>Webhook URL:</strong><br />
<code class="language-plaintext highlighter-rouge">http://localhost/v1/vcs/github/events</code></li>
</ul>

<p>For permissions, give:</p>

<ul>
  <li><strong>Contents:</strong> Read &amp; Write</li>
  <li><strong>Metadata:</strong> Read-only</li>
  <li>(Optional) Pull Request + Webhook permissions if you want richer automation later</li>
</ul>

<p>Create the app, then collect the important credentials:</p>

<ul>
  <li>App ID</li>
  <li>Client ID</li>
  <li>Client Secret</li>
  <li>Private Key (<code class="language-plaintext highlighter-rouge">.pem</code> file)</li>
</ul>

<p>These will be plugged into Appwrite next.</p>

<h2 id="2-add-credentials-to-appwrite">2. Add Credentials to Appwrite</h2>

<p>Open your Appwrite <code class="language-plaintext highlighter-rouge">.env</code> file and add:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">_APP_VCS_GITHUB_APP_NAME</span><span class="o">=</span>your-app-name
<span class="nv">_APP_VCS_GITHUB_APP_ID</span><span class="o">=</span>123456
<span class="nv">_APP_VCS_GITHUB_CLIENT_ID</span><span class="o">=</span>Iv1.xxxxx
<span class="nv">_APP_VCS_GITHUB_CLIENT_SECRET</span><span class="o">=</span>your-secret
<span class="nv">_APP_VCS_GITHUB_WEBHOOK_SECRET</span><span class="o">=</span>your-webhook-secret
</code></pre></div></div>

<p>For the private key, the easiest way is to base64 encode it:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat </span>myapp.private-key.pem | <span class="nb">base64</span> | <span class="nb">tr</span> <span class="nt">-d</span> <span class="s1">'\n'</span>
</code></pre></div></div>

<p>Then put that into:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">_APP_VCS_GITHUB_PRIVATE_KEY</span><span class="o">=</span><span class="s2">"base64-encoded-key"</span>
</code></pre></div></div>

<p>Restart Appwrite:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-compose down
docker-compose up <span class="nt">-d</span>
</code></pre></div></div>

<h2 id="3-install-the-github-app">3. Install the GitHub App</h2>

<p>Visit your GitHub App page → <strong>Install App</strong><br />
Choose your account and select the repo(s) you want to use.</p>

<h2 id="4-connect-a-repository-in-appwrite">4. Connect a Repository in Appwrite</h2>

<p>Open your local Appwrite console → navigate to the project → <strong>Functions</strong>.</p>

<p>While creating/editing a function, you’ll now see:</p>

<blockquote>
  <p><strong>Connect Git Repository</strong></p>
</blockquote>

<p>Click it → authenticate → pick your repo → choose a branch → set root directory and build steps.</p>

<p>Once connected, Appwrite will automatically deploy every time you push.</p>

<h2 id="5-test-everything">5. Test Everything</h2>

<p>Make a tiny change inside your repo, commit, and push:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push
</code></pre></div></div>

<p>Go back to the Appwrite Console → your function → <strong>Deployments</strong>.</p>

<p>You should see a new deployment triggered from your Git commit. If it’s green, everything works!</p>

<h2 id="-troubleshooting">🐞 Troubleshooting</h2>

<ul>
  <li><strong>Auth errors:</strong> Double-check values in <code class="language-plaintext highlighter-rouge">.env</code></li>
  <li><strong>Private key issues:</strong> Re-encode or regenerate</li>
  <li><strong>Deployments not triggering:</strong>
    <ul>
      <li>Localhost can’t receive GitHub webhooks</li>
      <li>Use ngrok if you want live push-triggered deployments</li>
    </ul>
  </li>
  <li><strong>Executor errors:</strong> Ensure executor hostname matches your docker-compose file</li>
</ul>

<h2 id="-final-thoughts">🎉 Final Thoughts</h2>

<p>Setting up VCS once makes your entire development workflow <em>so much better</em>. No more manual uploads or repetitive packaging — just write code, commit, push, and let Appwrite handle the rest.</p>

<p>If you’re working heavily with functions or sites locally, this setup is absolutely worth doing.</p>

<p>Happy building! 🚀</p>]]></content><author><name>Mustaque Ahmed</name><email>amustaque97@gmail.com</email></author><category term="appwrite" /><category term="github" /><category term="integration" /><category term="dev" /><summary type="html"><![CDATA[I’ll walk you through how to set up GitHub VCS integration for a local Appwrite instance — based on my own notes while configuring it.]]></summary></entry><entry><title type="html">Terraform Replace - The Modern Way to Rebuild Resources</title><link href="https://amustaque97.github.io/modern-way-to-recreate-terraform-resources/" rel="alternate" type="text/html" title="Terraform Replace - The Modern Way to Rebuild Resources" /><published>2025-11-11T00:00:00+05:30</published><updated>2025-11-11T00:00:00+05:30</updated><id>https://amustaque97.github.io/modern-way-to-recreate-terraform-resources</id><content type="html" xml:base="https://amustaque97.github.io/modern-way-to-recreate-terraform-resources/"><![CDATA[<h3 id="introduction">Introduction</h3>

<p>If you’ve used Terraform long enough, you probably remember the good old <code class="language-plaintext highlighter-rouge">terraform taint</code> and <code class="language-plaintext highlighter-rouge">untaint</code> commands.<br />
They were handy for forcing Terraform to rebuild specific resources when something went wrong.</p>

<p>But in modern Terraform (v0.15+), <strong>taint/untaint is deprecated</strong> — and we now have a better, more predictable way to do the same thing:<br />
<strong>the <code class="language-plaintext highlighter-rouge">-replace</code> flag.</strong> 🚀</p>

<h3 id="-the-story--when-a-resource-refuses-to-behave">🧠 The Story — When a Resource Refuses to Behave</h3>

<p>A few months ago, I was debugging a flaky EC2 instance.<br />
Terraform insisted,</p>
<blockquote>
  <p>“No changes. Everything is up to date.”</p>
</blockquote>

<p>Except it wasn’t.<br />
The instance had drifted — manually modified outside Terraform — and needed a rebuild.<br />
Previously, I would’ve used:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>terraform taint aws_instance.web
terraform apply
</code></pre></div></div>

<p>But since that’s deprecated, the modern approach is to use <code class="language-plaintext highlighter-rouge">-replace</code>.</p>

<h3 id="️-the-modern-replacement--terraform-apply--replace">⚙️ The Modern Replacement — <code class="language-plaintext highlighter-rouge">terraform apply -replace</code></h3>

<p>Instead of tainting the resource, you can now do:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>terraform apply <span class="nt">-replace</span><span class="o">=</span><span class="s2">"aws_instance.web"</span>
</code></pre></div></div>

<p>This tells Terraform <strong>explicitly</strong> to destroy and recreate the specified resource —<br />
no tainting, no state changes, just a clean rebuild during apply.</p>

<h3 id="-ascii-flow--how--replace-works">🔄 ASCII Flow — How <code class="language-plaintext highlighter-rouge">-replace</code> Works</h3>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+-----------------------+
| terraform plan        |
+----------+------------+
           |
           v
   (Detects Replace Flag)
           |
           v
+----------+------------+
| terraform apply        |
|  Destroy + Recreate    |
|  Only Targeted Resource|
+------------------------+
</code></pre></div></div>

<p>It’s simpler, safer, and keeps your state file clean.</p>

<h3 id="-why--replace-is-better">💡 Why <code class="language-plaintext highlighter-rouge">-replace</code> Is Better</h3>

<p>✅ <strong>No manual state edits</strong> — Unlike <code class="language-plaintext highlighter-rouge">taint</code>, this doesn’t mark anything in the state file.<br />
✅ <strong>Predictable behavior</strong> — You see exactly which resources will be replaced before applying.<br />
✅ <strong>One-liner control</strong> — Works with both <code class="language-plaintext highlighter-rouge">plan</code> and <code class="language-plaintext highlighter-rouge">apply</code>.<br />
✅ <strong>Supports multiple resources</strong> — Replace several resources in a single run.</p>

<p>Example:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>terraform apply <span class="nt">-replace</span><span class="o">=</span><span class="s2">"aws_instance.web"</span> <span class="nt">-replace</span><span class="o">=</span><span class="s2">"aws_security_group.web_sg"</span>
</code></pre></div></div>

<h3 id="-real-life-use-cases">🧩 Real-Life Use Cases</h3>

<ul>
  <li>A resource is stuck in an inconsistent or failed state</li>
  <li>You’ve made manual changes in the cloud provider console</li>
  <li>You need to re-provision a single resource without touching others</li>
  <li>A module upgrade requires fresh resources</li>
</ul>

<h3 id="-pro-tip--use-it-with-terraform-plan">🧠 Pro Tip — Use It with <code class="language-plaintext highlighter-rouge">terraform plan</code></h3>

<p>If you’re cautious (and you should be), always preview the changes before applying:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>terraform plan <span class="nt">-replace</span><span class="o">=</span><span class="s2">"aws_instance.web"</span>
</code></pre></div></div>

<p>This gives you a clear diff of what Terraform will destroy and recreate.</p>

<h3 id="️-things-to-keep-in-mind">⚠️ Things to Keep in Mind</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">-replace</code> is <strong>temporary</strong> — it only applies to the current run.</li>
  <li>Use it carefully in production — especially when replacing critical resources like databases.</li>
  <li>Combine with <code class="language-plaintext highlighter-rouge">create_before_destroy</code> (via lifecycle) if you need zero downtime.</li>
</ul>

<h3 id="-summary">🚀 Summary</h3>

<p>Terraform’s <code class="language-plaintext highlighter-rouge">-replace</code> flag is the modern, safer successor to <code class="language-plaintext highlighter-rouge">taint/untaint</code>.<br />
It’s designed for the same purpose — <strong>rebuilding problematic resources</strong> — but with clearer intent and cleaner state handling.</p>

<p>So next time you hear Terraform whisper, “No changes,” but you <em>know</em> something’s wrong…<br />
just replace it. 😉</p>

<h4 id="tldr">TL;DR</h4>

<table>
  <thead>
    <tr>
      <th>Command</th>
      <th>Description</th>
      <th>Status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">terraform taint &lt;resource&gt;</code></td>
      <td>Mark resource for recreation</td>
      <td>❌ Deprecated</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">terraform untaint &lt;resource&gt;</code></td>
      <td>Remove taint mark</td>
      <td>❌ Deprecated</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">terraform apply -replace=&lt;resource&gt;</code></td>
      <td>Recreate resource cleanly</td>
      <td>✅ Modern &amp; Recommended</td>
    </tr>
  </tbody>
</table>]]></content><author><name>Mustaque Ahmed</name><email>amustaque97@gmail.com</email></author><category term="Terraform" /><category term="DevOps" /><category term="IaC" /><category term="InfrastructureAsCode" /><category term="CloudEngineering" /><summary type="html"><![CDATA[A quick guide to Terraform’s -replace flag — the smarter, state-safe successor to taint and untaint]]></summary></entry><entry><title type="html">Terraform Lifecycle — Because “Destroy Everything” Isn’t Always the Best Plan</title><link href="https://amustaque97.github.io/terraform-lifecycles/" rel="alternate" type="text/html" title="Terraform Lifecycle — Because “Destroy Everything” Isn’t Always the Best Plan" /><published>2025-11-08T00:00:00+05:30</published><updated>2025-11-08T00:00:00+05:30</updated><id>https://amustaque97.github.io/terraform-lifecycles</id><content type="html" xml:base="https://amustaque97.github.io/terraform-lifecycles/"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>Ever applied a Terraform plan and suddenly watched it destroy and recreate a perfectly working resource? 😅<br />
That was me — until I discovered the power of <strong>Terraform lifecycle blocks</strong>.</p>

<p>If you’ve been working with Terraform for a while, you already know how declarative and powerful it is. But sometimes, Terraform’s default behavior can be… a bit too eager. That’s where lifecycle rules come in — to give you <em>control</em> over how Terraform creates, updates, and destroys your infrastructure resources.</p>

<hr />

<h2 id="-what-is-a-lifecycle-block">🧠 What Is a Lifecycle Block?</h2>

<p>A <strong>lifecycle block</strong> in Terraform allows you to tweak the default behavior of resource management.</p>

<p>You can think of it as Terraform’s etiquette manual — it helps Terraform know <em>when to act</em>, <em>what to skip</em>, and <em>what not to touch</em>.</p>

<p>Here’s what it looks like:</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">resource</span> <span class="s2">"aws_instance"</span> <span class="s2">"web"</span> <span class="p">{</span>
  <span class="nx">ami</span>           <span class="p">=</span> <span class="s2">"ami-123456"</span>
  <span class="nx">instance_type</span> <span class="p">=</span> <span class="s2">"t2.micro"</span>

  <span class="nx">lifecycle</span> <span class="p">{</span>
    <span class="nx">prevent_destroy</span>      <span class="p">=</span> <span class="kc">true</span>
    <span class="nx">create_before_destroy</span> <span class="p">=</span> <span class="kc">true</span>
    <span class="nx">ignore_changes</span>       <span class="p">=</span> <span class="p">[</span><span class="nx">tags</span><span class="p">]</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="️-lifecycle-arguments-explained">⚙️ Lifecycle Arguments Explained</h2>

<h3 id="1-prevent_destroy">1. <strong>prevent_destroy</strong></h3>
<p>If you’ve ever accidentally destroyed a production resource, this one’s your new best friend.</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">lifecycle</span> <span class="p">{</span>
  <span class="nx">prevent_destroy</span> <span class="p">=</span> <span class="kc">true</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Terraform will <em>refuse</em> to destroy this resource — unless you manually remove the flag. It’s your safety net against “oops” moments.</p>

<blockquote>
  <p>💡 Best used for databases, critical load balancers, and persistent storage.</p>
</blockquote>

<hr />

<h3 id="2-create_before_destroy">2. <strong>create_before_destroy</strong></h3>
<p>This one ensures <strong>zero downtime</strong> during replacements. Terraform will first create the new resource before destroying the old one.</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">lifecycle</span> <span class="p">{</span>
  <span class="nx">create_before_destroy</span> <span class="p">=</span> <span class="kc">true</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It’s extremely useful when dealing with resources like EC2 instances, load balancers, or anything in a production chain that needs to stay online.</p>

<blockquote>
  <p>🚀 Think of it as blue-green deployment, but for Terraform resources.</p>
</blockquote>

<hr />

<h3 id="3-ignore_changes">3. <strong>ignore_changes</strong></h3>
<p>Sometimes Terraform shows a diff for changes that don’t matter — like automatically assigned IPs or tags added by cloud providers.</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">lifecycle</span> <span class="p">{</span>
  <span class="nx">ignore_changes</span> <span class="p">=</span> <span class="p">[</span><span class="nx">tags</span><span class="p">,</span> <span class="nx">private_ip</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Terraform will simply ignore these attributes during plan/apply.</p>

<blockquote>
  <p>🧩 Perfect when using autoscaling groups, managed services, or provider-added metadata.</p>
</blockquote>

<hr />

<h2 id="-why-use-lifecycle">💡 Why Use Lifecycle?</h2>

<p>Because <strong>not all changes should be destructive</strong>.<br />
Terraform lifecycle gives you the control to:</p>

<p>✅ Prevent accidental data loss<br />
✅ Maintain zero downtime rollouts<br />
✅ Keep your plans clean from unnecessary updates<br />
✅ Align Terraform with real-world infra patterns</p>

<p>It’s not about changing Terraform’s nature — it’s about teaching it manners. 😄</p>

<hr />

<h2 id="️-things-to-keep-in-mind">⚠️ Things to Keep in Mind</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">prevent_destroy</code> can block legitimate deletions — use it wisely.</li>
  <li>Misusing <code class="language-plaintext highlighter-rouge">ignore_changes</code> may cause drift between your code and real infrastructure.</li>
  <li><code class="language-plaintext highlighter-rouge">create_before_destroy</code> doesn’t work if the provider doesn’t allow two resources with the same name/ID.</li>
</ul>

<hr />

<h2 id="-conclusion">🚀 Conclusion</h2>

<p>Terraform lifecycle blocks are one of those underrated features that can save you hours of debugging, unplanned downtime, and heart-stopping “why did this get deleted?” moments.</p>

<p>Once you start using them wisely, your infrastructure becomes more predictable — and Terraform becomes less of a rebel. 😉</p>]]></content><author><name>Mustaque Ahmed</name><email>amustaque97@gmail.com</email></author><category term="Terraform" /><category term="DevOps" /><category term="IaC" /><category term="InfrastructureAsCode" /><category term="CloudEngineering" /><summary type="html"><![CDATA[Learn how lifecycle blocks give you fine-grained control over creation, updates, and deletions — making your Terraform deployments safer and smarter.]]></summary></entry><entry><title type="html">Send Alerts to Slack/Discord When a Kubernetes Pod Restarts</title><link href="https://amustaque97.github.io/send-k8s-alerts-on-slack/" rel="alternate" type="text/html" title="Send Alerts to Slack/Discord When a Kubernetes Pod Restarts" /><published>2025-11-05T00:00:00+05:30</published><updated>2025-11-05T00:00:00+05:30</updated><id>https://amustaque97.github.io/send-k8s-alerts-on-slack</id><content type="html" xml:base="https://amustaque97.github.io/send-k8s-alerts-on-slack/"><![CDATA[<h2 id="main-takeaway">Main Takeaway</h2>

<p>Monitor Kubernetes pod restarts using Grafana and Prometheus, then enrich your Slack/Discord alerts with real-time Kubernetes events and recent pod logs by leveraging custom webhook payloads and integration with Loki. This comprehensive guide provides production-ready implementation steps.</p>

<h2 id="introduction">Introduction</h2>

<p>Effective Kubernetes incident response requires not just detecting pod restarts, but understanding <em>why</em> they occurred. Combining pod restart metrics with Kubernetes cluster events and application logs provides operators with complete context for rapid troubleshooting. This technical deep-dive covers:</p>

<ul>
  <li>Monitoring pod restarts via Prometheus and kube-state-metrics</li>
  <li>Capturing Kubernetes events using event exporters</li>
  <li>Aggregating pod logs with Grafana Loki</li>
  <li>Crafting rich Slack/Discord webhook payloads that include events and logs</li>
  <li>Building a complete alerting workflow from detection to notification</li>
</ul>

<h2 id="part-1-foundational-monitoring-setup">Part 1: Foundational Monitoring Setup</h2>

<h3 id="step-1-deploy-kube-state-metrics-and-prometheus">Step 1: Deploy kube-state-metrics and Prometheus</h3>

<p>Deploy kube-state-metrics to expose pod restart counts:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl apply <span class="nt">-f</span> https://github.com/kubernetes/kube-state-metrics/releases/latest/download/kube-state-metrics.yaml
</code></pre></div></div>

<p>Verify that Prometheus scrapes the metric:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kube_pod_container_status_restarts_total{namespace="default",pod="nginx-deployment-66b6c48dd5-abc123",container="nginx"}
</code></pre></div></div>

<h2 id="part-2-collecting-kubernetes-events">Part 2: Collecting Kubernetes Events</h2>

<p>Kubernetes events provide critical context about pod state changes. They’re ephemeral (default TTL: 1 hour) and stored in etcd, so exporting them is essential for long-term analysis.</p>

<h3 id="understanding-kubernetes-events">Understanding Kubernetes Events</h3>

<p>Kubernetes events capture state transitions:</p>

<ul>
  <li><strong>Pod Created</strong>: When a pod is scheduled</li>
  <li><strong>Pod Failed</strong>: When a container exits with non-zero status</li>
  <li><strong>BackOff</strong>: When restarts exceed retry limits</li>
  <li><strong>Killing</strong>: When a pod is terminated</li>
  <li><strong>Liveness Probe Failed</strong>: When health checks fail</li>
</ul>

<p>Each event contains:</p>

<ul>
  <li><strong>Reason</strong>: Event type (e.g., <code class="language-plaintext highlighter-rouge">Backoff</code>, <code class="language-plaintext highlighter-rouge">Failed</code>)</li>
  <li><strong>Message</strong>: Human-readable description</li>
  <li><strong>Count</strong>: How many times the event occurred</li>
  <li><strong>Source</strong>: Which component reported (e.g., <code class="language-plaintext highlighter-rouge">kubelet</code>, <code class="language-plaintext highlighter-rouge">kube-controller-manager</code>)</li>
  <li><strong>Timestamp</strong>: When the event occurred</li>
</ul>

<h3 id="deploy-event-exporter">Deploy Event Exporter</h3>

<p>Use <strong>kubernetes-event-exporter</strong> (open-source) or <strong>kube-events</strong> to bridge Kubernetes events to Prometheus:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Add Helm repository</span>
helm repo add resmoio https://resmoio.github.io/helm-charts
helm repo update

<span class="c"># Install event exporter</span>
helm <span class="nb">install </span>event-exporter resmoio/kubernetes-event-exporter <span class="se">\</span>
  <span class="nt">--namespace</span> monitoring <span class="se">\</span>
  <span class="nt">--set</span> <span class="nv">logLevel</span><span class="o">=</span>info
</code></pre></div></div>

<p>The event exporter exposes these metrics:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kube_event_count{
  involved_object_kind="Pod",
  involved_object_name="my-pod",
  involved_object_namespace="default",
  reason="Backoff",
  type="Warning"
}

kube_event_unique_events_total{...}
</code></pre></div></div>

<h3 id="query-recent-events-for-a-pod">Query Recent Events for a Pod</h3>

<p>In Prometheus, query pod-specific events:</p>

<pre><code class="language-promql">kube_event_count{involved_object_name=~"my-pod.*",involved_object_kind="Pod"}
</code></pre>

<h2 id="part-3-aggregating-pod-logs-with-grafana-loki">Part 3: Aggregating Pod Logs with Grafana Loki</h2>

<p>Loki indexes logs by label, not by content, making it ideal for Kubernetes log aggregation.</p>

<h3 id="deploy-loki-and-alloy-log-collector">Deploy Loki and Alloy (Log Collector)</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Add Grafana Helm repository</span>
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

<span class="c"># Install Loki Stack (includes Loki and Alloy)</span>
helm <span class="nb">install </span>loki grafana/loki-stack <span class="se">\</span>
  <span class="nt">--namespace</span> loki <span class="se">\</span>
  <span class="nt">--create-namespace</span> <span class="se">\</span>
  <span class="nt">--set</span> loki.persistence.enabled<span class="o">=</span><span class="nb">true</span> <span class="se">\</span>
  <span class="nt">--set</span> promtail.enabled<span class="o">=</span><span class="nb">true</span>
</code></pre></div></div>

<h3 id="configure-loki-datasource-in-grafana">Configure Loki Datasource in Grafana</h3>

<ol>
  <li>Navigate to <strong>Configuration → Data Sources</strong></li>
  <li>Add <strong>Loki</strong> as a datasource</li>
  <li>Set URL to <code class="language-plaintext highlighter-rouge">http://loki:3100</code> (adjust for your setup)</li>
</ol>

<h3 id="query-pod-logs-in-logql">Query Pod Logs in LogQL</h3>

<p>Query recent logs for a restarted pod:</p>

<pre><code class="language-logql">{namespace="default",pod="nginx-deployment-66b6c48dd5-abc123"} | json
</code></pre>

<p>Extract error-level logs:</p>

<pre><code class="language-logql">{namespace="default",pod="nginx-deployment-66b6c48dd5-abc123"} | json level="error"
</code></pre>

<p>Limit to last 100 lines:</p>

<pre><code class="language-logql">{namespace="default",pod="nginx-deployment-66b6c48dd5-abc123"} | line_format "" | limit 100
</code></pre>

<h2 id="part-4-creating-rich-alert-rules-in-grafana">Part 4: Creating Rich Alert Rules in Grafana</h2>

<h3 id="create-alert-rule-with-context">Create Alert Rule with Context</h3>

<p>In Grafana Alerting, create a rule that triggers on pod restarts:</p>

<p><strong>Rule Configuration:</strong></p>

<ul>
  <li><strong>Query A (Prometheus):</strong>
    <pre><code class="language-promql">increase(kube_pod_container_status_restarts_total[5m]) &gt; 0
</code></pre>
    <p>Triggers when any pod restarts within 5 minutes.</p>
  </li>
  <li>
    <p><strong>Alert Condition:</strong> Status is firing</p>
  </li>
  <li><strong>Evaluation Interval:</strong> 1 minute</li>
</ul>

<p><strong>Annotations (Alert Metadata):</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">summary</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Pod</span><span class="nv">  </span><span class="s">restarted"</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Pod</span><span class="nv">  </span><span class="s">in</span><span class="nv"> </span><span class="s">namespace</span><span class="nv">  </span><span class="s">has</span><span class="nv"> </span><span class="s">restarted</span><span class="nv">  </span><span class="s">times</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">last</span><span class="nv"> </span><span class="s">5</span><span class="nv"> </span><span class="s">minutes."</span>
<span class="na">pod_name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>
<span class="na">namespace</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>
<span class="na">restart_count</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>
</code></pre></div></div>

<p><strong>Labels (Routing):</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">severity</span><span class="pi">:</span> <span class="s2">"</span><span class="s">critical"</span>
<span class="na">component</span><span class="pi">:</span> <span class="s2">"</span><span class="s">pod-restart-alert"</span>
</code></pre></div></div>

<h2 id="part-5-webhook-endpoint-to-enrich-alerts-with-events-and-logs">Part 5: Webhook Endpoint to Enrich Alerts with Events and Logs</h2>

<p>Grafana sends webhook payloads to your custom endpoint. This endpoint fetches additional context (events and logs) before forwarding to Slack/Discord.</p>

<h3 id="python-webhook-receiver">Python Webhook Receiver</h3>

<p>Create a Flask application to handle Grafana webhooks:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">Flask</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="n">jsonify</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">requests</span>
<span class="kn">from</span> <span class="nn">kubernetes</span> <span class="kn">import</span> <span class="n">client</span><span class="p">,</span> <span class="n">config</span><span class="p">,</span> <span class="n">watch</span>
<span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span><span class="p">,</span> <span class="n">timedelta</span>
<span class="kn">import</span> <span class="nn">subprocess</span>
<span class="kn">import</span> <span class="nn">os</span>

<span class="n">app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="n">__name__</span><span class="p">)</span>

<span class="c1"># Load Kubernetes config
</span><span class="n">config</span><span class="p">.</span><span class="n">load_incluster_config</span><span class="p">()</span>  <span class="c1"># For in-cluster pods
# OR: config.load_kube_config()  # For local development
</span>
<span class="n">v1</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="n">CoreV1Api</span><span class="p">()</span>
<span class="n">apps_v1</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="n">AppsV1Api</span><span class="p">()</span>

<span class="k">def</span> <span class="nf">get_pod_recent_events</span><span class="p">(</span><span class="n">namespace</span><span class="p">,</span> <span class="n">pod_name</span><span class="p">):</span>
    <span class="s">"""Fetch recent Kubernetes events for a pod"""</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">events</span> <span class="o">=</span> <span class="n">v1</span><span class="p">.</span><span class="n">list_namespaced_event</span><span class="p">(</span><span class="n">namespace</span><span class="o">=</span><span class="n">namespace</span><span class="p">)</span>
        <span class="n">pod_events</span> <span class="o">=</span> <span class="p">[</span>
            <span class="n">e</span> <span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="n">events</span><span class="p">.</span><span class="n">items</span>
            <span class="k">if</span> <span class="n">e</span><span class="p">.</span><span class="n">involved_object</span><span class="p">.</span><span class="n">name</span> <span class="o">==</span> <span class="n">pod_name</span>
            <span class="ow">and</span> <span class="n">e</span><span class="p">.</span><span class="n">involved_object</span><span class="p">.</span><span class="n">kind</span> <span class="o">==</span> <span class="s">"Pod"</span>
        <span class="p">]</span>
        <span class="c1"># Sort by timestamp (most recent first)
</span>        <span class="n">pod_events</span><span class="p">.</span><span class="n">sort</span><span class="p">(</span>
            <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">e</span><span class="p">:</span> <span class="n">e</span><span class="p">.</span><span class="n">last_timestamp</span> <span class="ow">or</span> <span class="n">e</span><span class="p">.</span><span class="n">first_timestamp</span><span class="p">,</span>
            <span class="n">reverse</span><span class="o">=</span><span class="bp">True</span>
        <span class="p">)</span>
        <span class="k">return</span> <span class="n">pod_events</span><span class="p">[:</span><span class="mi">5</span><span class="p">]</span>  <span class="c1"># Return last 5 events
</span>    <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Error fetching events: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
        <span class="k">return</span> <span class="p">[]</span>

<span class="k">def</span> <span class="nf">get_pod_logs</span><span class="p">(</span><span class="n">namespace</span><span class="p">,</span> <span class="n">pod_name</span><span class="p">,</span> <span class="n">container_name</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">tail_lines</span><span class="o">=</span><span class="mi">50</span><span class="p">):</span>
    <span class="s">"""Fetch recent pod logs using kubectl"""</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">cmd</span> <span class="o">=</span> <span class="p">[</span>
            <span class="s">"kubectl"</span><span class="p">,</span> <span class="s">"logs"</span><span class="p">,</span>
            <span class="sa">f</span><span class="s">"--namespace=</span><span class="si">{</span><span class="n">namespace</span><span class="si">}</span><span class="s">"</span><span class="p">,</span>
            <span class="n">pod_name</span><span class="p">,</span>
            <span class="sa">f</span><span class="s">"--tail=</span><span class="si">{</span><span class="n">tail_lines</span><span class="si">}</span><span class="s">"</span>
        <span class="p">]</span>
        <span class="k">if</span> <span class="n">container_name</span><span class="p">:</span>
            <span class="n">cmd</span><span class="p">.</span><span class="n">extend</span><span class="p">([</span><span class="s">"-c"</span><span class="p">,</span> <span class="n">container_name</span><span class="p">])</span>
        
        <span class="n">result</span> <span class="o">=</span> <span class="n">subprocess</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">capture_output</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">text</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">result</span><span class="p">.</span><span class="n">stdout</span> <span class="k">if</span> <span class="n">result</span><span class="p">.</span><span class="n">returncode</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">else</span> <span class="sa">f</span><span class="s">"Error: </span><span class="si">{</span><span class="n">result</span><span class="p">.</span><span class="n">stderr</span><span class="si">}</span><span class="s">"</span>
    <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="k">return</span> <span class="sa">f</span><span class="s">"Failed to retrieve logs: </span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="si">}</span><span class="s">"</span>

<span class="k">def</span> <span class="nf">get_pod_previous_logs</span><span class="p">(</span><span class="n">namespace</span><span class="p">,</span> <span class="n">pod_name</span><span class="p">,</span> <span class="n">container_name</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">tail_lines</span><span class="o">=</span><span class="mi">50</span><span class="p">):</span>
    <span class="s">"""Fetch logs from previous container instance (for crashes)"""</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">cmd</span> <span class="o">=</span> <span class="p">[</span>
            <span class="s">"kubectl"</span><span class="p">,</span> <span class="s">"logs"</span><span class="p">,</span>
            <span class="sa">f</span><span class="s">"--namespace=</span><span class="si">{</span><span class="n">namespace</span><span class="si">}</span><span class="s">"</span><span class="p">,</span>
            <span class="n">pod_name</span><span class="p">,</span>
            <span class="s">"--previous"</span><span class="p">,</span>
            <span class="sa">f</span><span class="s">"--tail=</span><span class="si">{</span><span class="n">tail_lines</span><span class="si">}</span><span class="s">"</span>
        <span class="p">]</span>
        <span class="k">if</span> <span class="n">container_name</span><span class="p">:</span>
            <span class="n">cmd</span><span class="p">.</span><span class="n">extend</span><span class="p">([</span><span class="s">"-c"</span><span class="p">,</span> <span class="n">container_name</span><span class="p">])</span>
        
        <span class="n">result</span> <span class="o">=</span> <span class="n">subprocess</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">capture_output</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">text</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">result</span><span class="p">.</span><span class="n">stdout</span> <span class="k">if</span> <span class="n">result</span><span class="p">.</span><span class="n">returncode</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">else</span> <span class="bp">None</span>
    <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="k">return</span> <span class="bp">None</span>

<span class="k">def</span> <span class="nf">format_events_for_slack</span><span class="p">(</span><span class="n">events</span><span class="p">):</span>
    <span class="s">"""Format Kubernetes events as Slack message blocks"""</span>
    <span class="n">blocks</span> <span class="o">=</span> <span class="p">[]</span>
    
    <span class="k">for</span> <span class="n">event</span> <span class="ow">in</span> <span class="n">events</span><span class="p">:</span>
        <span class="n">blocks</span><span class="p">.</span><span class="n">append</span><span class="p">({</span>
            <span class="s">"type"</span><span class="p">:</span> <span class="s">"section"</span><span class="p">,</span>
            <span class="s">"text"</span><span class="p">:</span> <span class="p">{</span>
                <span class="s">"type"</span><span class="p">:</span> <span class="s">"mrkdwn"</span><span class="p">,</span>
                <span class="s">"text"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"*Reason:* `</span><span class="si">{</span><span class="n">event</span><span class="p">.</span><span class="n">reason</span><span class="si">}</span><span class="s">`</span><span class="se">\n</span><span class="s">*Message:* </span><span class="si">{</span><span class="n">event</span><span class="p">.</span><span class="n">message</span><span class="si">}</span><span class="se">\n</span><span class="s">*Count:* </span><span class="si">{</span><span class="n">event</span><span class="p">.</span><span class="n">count</span><span class="si">}</span><span class="se">\n</span><span class="s">*Time:* </span><span class="si">{</span><span class="n">event</span><span class="p">.</span><span class="n">last_timestamp</span> <span class="ow">or</span> <span class="n">event</span><span class="p">.</span><span class="n">first_timestamp</span><span class="si">}</span><span class="s">"</span>
            <span class="p">}</span>
        <span class="p">})</span>
    
    <span class="k">return</span> <span class="n">blocks</span>

<span class="k">def</span> <span class="nf">send_to_slack</span><span class="p">(</span><span class="n">webhook_url</span><span class="p">,</span> <span class="n">message_payload</span><span class="p">):</span>
    <span class="s">"""Send message to Slack webhook"""</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="n">webhook_url</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">message_payload</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">response</span><span class="p">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
    <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Slack send error: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
        <span class="k">return</span> <span class="bp">False</span>

<span class="k">def</span> <span class="nf">send_to_discord</span><span class="p">(</span><span class="n">webhook_url</span><span class="p">,</span> <span class="n">message_payload</span><span class="p">):</span>
    <span class="s">"""Send message to Discord webhook"""</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="n">webhook_url</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">message_payload</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">response</span><span class="p">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">204</span>
    <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Discord send error: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
        <span class="k">return</span> <span class="bp">False</span>

<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/alert'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'POST'</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">handle_alert</span><span class="p">():</span>
    <span class="s">"""Handle Grafana webhook alert"""</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">payload</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="n">json</span>
        
        <span class="c1"># Extract pod information from alert
</span>        <span class="n">alerts</span> <span class="o">=</span> <span class="n">payload</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'alerts'</span><span class="p">,</span> <span class="p">[])</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">alerts</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">jsonify</span><span class="p">({</span><span class="s">"error"</span><span class="p">:</span> <span class="s">"No alerts found"</span><span class="p">}),</span> <span class="mi">400</span>
        
        <span class="n">alert</span> <span class="o">=</span> <span class="n">alerts</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
        <span class="n">labels</span> <span class="o">=</span> <span class="n">alert</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'labels'</span><span class="p">,</span> <span class="p">{})</span>
        
        <span class="n">pod_name</span> <span class="o">=</span> <span class="n">labels</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'pod'</span><span class="p">,</span> <span class="s">'unknown'</span><span class="p">)</span>
        <span class="n">namespace</span> <span class="o">=</span> <span class="n">labels</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'namespace'</span><span class="p">,</span> <span class="s">'default'</span><span class="p">)</span>
        <span class="n">restart_count</span> <span class="o">=</span> <span class="n">alert</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'values'</span><span class="p">,</span> <span class="p">{}).</span><span class="n">get</span><span class="p">(</span><span class="s">'A'</span><span class="p">,</span> <span class="p">{}).</span><span class="n">get</span><span class="p">(</span><span class="s">'Value'</span><span class="p">,</span> <span class="s">'unknown'</span><span class="p">)</span>
        
        <span class="c1"># Fetch enrichment data
</span>        <span class="n">pod_events</span> <span class="o">=</span> <span class="n">get_pod_recent_events</span><span class="p">(</span><span class="n">namespace</span><span class="p">,</span> <span class="n">pod_name</span><span class="p">)</span>
        <span class="n">current_logs</span> <span class="o">=</span> <span class="n">get_pod_logs</span><span class="p">(</span><span class="n">namespace</span><span class="p">,</span> <span class="n">pod_name</span><span class="p">,</span> <span class="n">tail_lines</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span>
        <span class="n">previous_logs</span> <span class="o">=</span> <span class="n">get_pod_previous_logs</span><span class="p">(</span><span class="n">namespace</span><span class="p">,</span> <span class="n">pod_name</span><span class="p">,</span> <span class="n">tail_lines</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span>
        
        <span class="c1"># Determine target platform from query parameter
</span>        <span class="n">target</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="n">args</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'target'</span><span class="p">,</span> <span class="s">'slack'</span><span class="p">)</span>  <span class="c1"># slack or discord
</span>        <span class="n">webhook_url</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">getenv</span><span class="p">(</span><span class="sa">f</span><span class="s">'</span><span class="si">{</span><span class="n">target</span><span class="p">.</span><span class="n">upper</span><span class="p">()</span><span class="si">}</span><span class="s">_WEBHOOK_URL'</span><span class="p">)</span>
        
        <span class="k">if</span> <span class="ow">not</span> <span class="n">webhook_url</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">jsonify</span><span class="p">({</span><span class="s">"error"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">target</span><span class="si">}</span><span class="s"> webhook URL not configured"</span><span class="p">}),</span> <span class="mi">500</span>
        
        <span class="c1"># Build message payload
</span>        <span class="k">if</span> <span class="n">target</span><span class="p">.</span><span class="n">lower</span><span class="p">()</span> <span class="o">==</span> <span class="s">'slack'</span><span class="p">:</span>
            <span class="n">message</span> <span class="o">=</span> <span class="n">build_slack_message</span><span class="p">(</span>
                <span class="n">pod_name</span><span class="p">,</span> <span class="n">namespace</span><span class="p">,</span> <span class="n">restart_count</span><span class="p">,</span> <span class="n">pod_events</span><span class="p">,</span> <span class="n">current_logs</span><span class="p">,</span> <span class="n">previous_logs</span>
            <span class="p">)</span>
            <span class="n">success</span> <span class="o">=</span> <span class="n">send_to_slack</span><span class="p">(</span><span class="n">webhook_url</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
        <span class="k">else</span><span class="p">:</span>  <span class="c1"># discord
</span>            <span class="n">message</span> <span class="o">=</span> <span class="n">build_discord_message</span><span class="p">(</span>
                <span class="n">pod_name</span><span class="p">,</span> <span class="n">namespace</span><span class="p">,</span> <span class="n">restart_count</span><span class="p">,</span> <span class="n">pod_events</span><span class="p">,</span> <span class="n">current_logs</span><span class="p">,</span> <span class="n">previous_logs</span>
            <span class="p">)</span>
            <span class="n">success</span> <span class="o">=</span> <span class="n">send_to_discord</span><span class="p">(</span><span class="n">webhook_url</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
        
        <span class="k">return</span> <span class="n">jsonify</span><span class="p">({</span><span class="s">"success"</span><span class="p">:</span> <span class="n">success</span><span class="p">}),</span> <span class="mi">200</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="mi">500</span>
    
    <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Error handling alert: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">jsonify</span><span class="p">({</span><span class="s">"error"</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)}),</span> <span class="mi">500</span>

<span class="k">def</span> <span class="nf">build_slack_message</span><span class="p">(</span><span class="n">pod_name</span><span class="p">,</span> <span class="n">namespace</span><span class="p">,</span> <span class="n">restart_count</span><span class="p">,</span> <span class="n">events</span><span class="p">,</span> <span class="n">logs</span><span class="p">,</span> <span class="n">previous_logs</span><span class="p">):</span>
    <span class="s">"""Build Slack message with events and logs"""</span>
    
    <span class="c1"># Format logs (truncate to avoid exceeding Slack limits)
</span>    <span class="n">logs_text</span> <span class="o">=</span> <span class="n">logs</span><span class="p">[:</span><span class="mi">1500</span><span class="p">]</span> <span class="k">if</span> <span class="n">logs</span> <span class="k">else</span> <span class="s">"No logs available"</span>
    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">logs_text</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1000</span><span class="p">:</span>
        <span class="n">logs_text</span> <span class="o">=</span> <span class="n">logs_text</span><span class="p">[:</span><span class="mi">1000</span><span class="p">]</span> <span class="o">+</span> <span class="s">"</span><span class="se">\n</span><span class="s">... (truncated)"</span>
    
    <span class="n">previous_logs_text</span> <span class="o">=</span> <span class="n">previous_logs</span><span class="p">[:</span><span class="mi">1500</span><span class="p">]</span> <span class="k">if</span> <span class="n">previous_logs</span> <span class="k">else</span> <span class="s">"No previous logs"</span>
    <span class="k">if</span> <span class="n">previous_logs_text</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">previous_logs_text</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">800</span><span class="p">:</span>
        <span class="n">previous_logs_text</span> <span class="o">=</span> <span class="n">previous_logs_text</span><span class="p">[:</span><span class="mi">800</span><span class="p">]</span> <span class="o">+</span> <span class="s">"</span><span class="se">\n</span><span class="s">... (truncated)"</span>
    
    <span class="c1"># Format events
</span>    <span class="n">events_text</span> <span class="o">=</span> <span class="s">""</span>
    <span class="k">for</span> <span class="n">event</span> <span class="ow">in</span> <span class="n">events</span><span class="p">:</span>
        <span class="n">events_text</span> <span class="o">+=</span> <span class="sa">f</span><span class="s">"• *</span><span class="si">{</span><span class="n">event</span><span class="p">.</span><span class="n">reason</span><span class="si">}</span><span class="s">*: </span><span class="si">{</span><span class="n">event</span><span class="p">.</span><span class="n">message</span><span class="si">}</span><span class="s"> (Count: </span><span class="si">{</span><span class="n">event</span><span class="p">.</span><span class="n">count</span><span class="si">}</span><span class="s">)</span><span class="se">\n</span><span class="s">"</span>
    
    <span class="n">events_text</span> <span class="o">=</span> <span class="n">events_text</span> <span class="ow">or</span> <span class="s">"No recent events"</span>
    
    <span class="k">return</span> <span class="p">{</span>
        <span class="s">"text"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"🚨 Pod Restart Alert: </span><span class="si">{</span><span class="n">pod_name</span><span class="si">}</span><span class="s">"</span><span class="p">,</span>
        <span class="s">"blocks"</span><span class="p">:</span> <span class="p">[</span>
            <span class="p">{</span>
                <span class="s">"type"</span><span class="p">:</span> <span class="s">"header"</span><span class="p">,</span>
                <span class="s">"text"</span><span class="p">:</span> <span class="p">{</span>
                    <span class="s">"type"</span><span class="p">:</span> <span class="s">"plain_text"</span><span class="p">,</span>
                    <span class="s">"text"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"Pod Restart: </span><span class="si">{</span><span class="n">pod_name</span><span class="si">}</span><span class="s">"</span><span class="p">,</span>
                    <span class="s">"emoji"</span><span class="p">:</span> <span class="bp">True</span>
                <span class="p">}</span>
            <span class="p">},</span>
            <span class="p">{</span>
                <span class="s">"type"</span><span class="p">:</span> <span class="s">"section"</span><span class="p">,</span>
                <span class="s">"fields"</span><span class="p">:</span> <span class="p">[</span>
                    <span class="p">{</span>
                        <span class="s">"type"</span><span class="p">:</span> <span class="s">"mrkdwn"</span><span class="p">,</span>
                        <span class="s">"text"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"*Pod:*</span><span class="se">\n</span><span class="si">{</span><span class="n">pod_name</span><span class="si">}</span><span class="s">"</span>
                    <span class="p">},</span>
                    <span class="p">{</span>
                        <span class="s">"type"</span><span class="p">:</span> <span class="s">"mrkdwn"</span><span class="p">,</span>
                        <span class="s">"text"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"*Namespace:*</span><span class="se">\n</span><span class="si">{</span><span class="n">namespace</span><span class="si">}</span><span class="s">"</span>
                    <span class="p">},</span>
                    <span class="p">{</span>
                        <span class="s">"type"</span><span class="p">:</span> <span class="s">"mrkdwn"</span><span class="p">,</span>
                        <span class="s">"text"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"*Restarts (5m):*</span><span class="se">\n</span><span class="si">{</span><span class="n">restart_count</span><span class="si">}</span><span class="s">"</span>
                    <span class="p">},</span>
                    <span class="p">{</span>
                        <span class="s">"type"</span><span class="p">:</span> <span class="s">"mrkdwn"</span><span class="p">,</span>
                        <span class="s">"text"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"*Time:*</span><span class="se">\n</span><span class="si">{</span><span class="n">datetime</span><span class="p">.</span><span class="n">utcnow</span><span class="p">().</span><span class="n">isoformat</span><span class="p">()</span><span class="si">}</span><span class="s">"</span>
                    <span class="p">}</span>
                <span class="p">]</span>
            <span class="p">},</span>
            <span class="p">{</span>
                <span class="s">"type"</span><span class="p">:</span> <span class="s">"section"</span><span class="p">,</span>
                <span class="s">"text"</span><span class="p">:</span> <span class="p">{</span>
                    <span class="s">"type"</span><span class="p">:</span> <span class="s">"mrkdwn"</span><span class="p">,</span>
                    <span class="s">"text"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"*Recent Events:*</span><span class="se">\n</span><span class="si">{</span><span class="n">events_text</span><span class="si">}</span><span class="s">"</span>
                <span class="p">}</span>
            <span class="p">},</span>
            <span class="p">{</span>
                <span class="s">"type"</span><span class="p">:</span> <span class="s">"section"</span><span class="p">,</span>
                <span class="s">"text"</span><span class="p">:</span> <span class="p">{</span>
                    <span class="s">"type"</span><span class="p">:</span> <span class="s">"mrkdwn"</span><span class="p">,</span>
                    <span class="s">"text"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"*Current Logs (Last 30 lines):*</span><span class="se">\n</span><span class="s">```</span><span class="si">{</span><span class="n">logs_text</span><span class="si">}</span><span class="s">```"</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">]</span>
    <span class="p">}</span>
    
    <span class="c1"># Add previous logs section if available
</span>    <span class="k">if</span> <span class="n">previous_logs</span><span class="p">:</span>
        <span class="k">return</span> <span class="p">{</span>
            <span class="o">**</span><span class="p">{</span>
                <span class="s">"text"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"🚨 Pod Restart Alert: </span><span class="si">{</span><span class="n">pod_name</span><span class="si">}</span><span class="s">"</span><span class="p">,</span>
                <span class="s">"blocks"</span><span class="p">:</span> <span class="p">[</span>
                    <span class="p">{</span>
                        <span class="s">"type"</span><span class="p">:</span> <span class="s">"header"</span><span class="p">,</span>
                        <span class="s">"text"</span><span class="p">:</span> <span class="p">{</span>
                            <span class="s">"type"</span><span class="p">:</span> <span class="s">"plain_text"</span><span class="p">,</span>
                            <span class="s">"text"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"Pod Restart: </span><span class="si">{</span><span class="n">pod_name</span><span class="si">}</span><span class="s">"</span><span class="p">,</span>
                            <span class="s">"emoji"</span><span class="p">:</span> <span class="bp">True</span>
                        <span class="p">}</span>
                    <span class="p">},</span>
                    <span class="p">{</span>
                        <span class="s">"type"</span><span class="p">:</span> <span class="s">"section"</span><span class="p">,</span>
                        <span class="s">"fields"</span><span class="p">:</span> <span class="p">[</span>
                            <span class="p">{</span>
                                <span class="s">"type"</span><span class="p">:</span> <span class="s">"mrkdwn"</span><span class="p">,</span>
                                <span class="s">"text"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"*Pod:*</span><span class="se">\n</span><span class="si">{</span><span class="n">pod_name</span><span class="si">}</span><span class="s">"</span>
                            <span class="p">},</span>
                            <span class="p">{</span>
                                <span class="s">"type"</span><span class="p">:</span> <span class="s">"mrkdwn"</span><span class="p">,</span>
                                <span class="s">"text"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"*Namespace:*</span><span class="se">\n</span><span class="si">{</span><span class="n">namespace</span><span class="si">}</span><span class="s">"</span>
                            <span class="p">},</span>
                            <span class="p">{</span>
                                <span class="s">"type"</span><span class="p">:</span> <span class="s">"mrkdwn"</span><span class="p">,</span>
                                <span class="s">"text"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"*Restarts (5m):*</span><span class="se">\n</span><span class="si">{</span><span class="n">restart_count</span><span class="si">}</span><span class="s">"</span>
                            <span class="p">},</span>
                            <span class="p">{</span>
                                <span class="s">"type"</span><span class="p">:</span> <span class="s">"mrkdwn"</span><span class="p">,</span>
                                <span class="s">"text"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"*Time:*</span><span class="se">\n</span><span class="si">{</span><span class="n">datetime</span><span class="p">.</span><span class="n">utcnow</span><span class="p">().</span><span class="n">isoformat</span><span class="p">()</span><span class="si">}</span><span class="s">"</span>
                            <span class="p">}</span>
                        <span class="p">]</span>
                    <span class="p">},</span>
                    <span class="p">{</span>
                        <span class="s">"type"</span><span class="p">:</span> <span class="s">"section"</span><span class="p">,</span>
                        <span class="s">"text"</span><span class="p">:</span> <span class="p">{</span>
                            <span class="s">"type"</span><span class="p">:</span> <span class="s">"mrkdwn"</span><span class="p">,</span>
                            <span class="s">"text"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"*Recent Events:*</span><span class="se">\n</span><span class="si">{</span><span class="n">events_text</span><span class="si">}</span><span class="s">"</span>
                        <span class="p">}</span>
                    <span class="p">},</span>
                    <span class="p">{</span>
                        <span class="s">"type"</span><span class="p">:</span> <span class="s">"section"</span><span class="p">,</span>
                        <span class="s">"text"</span><span class="p">:</span> <span class="p">{</span>
                            <span class="s">"type"</span><span class="p">:</span> <span class="s">"mrkdwn"</span><span class="p">,</span>
                            <span class="s">"text"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"*Current Logs:*</span><span class="se">\n</span><span class="s">```</span><span class="si">{</span><span class="n">logs_text</span><span class="si">}</span><span class="s">```"</span>
                        <span class="p">}</span>
                    <span class="p">},</span>
                    <span class="p">{</span>
                        <span class="s">"type"</span><span class="p">:</span> <span class="s">"section"</span><span class="p">,</span>
                        <span class="s">"text"</span><span class="p">:</span> <span class="p">{</span>
                            <span class="s">"type"</span><span class="p">:</span> <span class="s">"mrkdwn"</span><span class="p">,</span>
                            <span class="s">"text"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"*Previous Logs (before restart):*</span><span class="se">\n</span><span class="s">```</span><span class="si">{</span><span class="n">previous_logs_text</span><span class="si">}</span><span class="s">```"</span>
                        <span class="p">}</span>
                    <span class="p">}</span>
                <span class="p">]</span>
            <span class="p">}</span>
        <span class="p">}</span>

<span class="k">def</span> <span class="nf">build_discord_message</span><span class="p">(</span><span class="n">pod_name</span><span class="p">,</span> <span class="n">namespace</span><span class="p">,</span> <span class="n">restart_count</span><span class="p">,</span> <span class="n">events</span><span class="p">,</span> <span class="n">logs</span><span class="p">,</span> <span class="n">previous_logs</span><span class="p">):</span>
    <span class="s">"""Build Discord embed message with events and logs"""</span>
    
    <span class="c1"># Format logs
</span>    <span class="n">logs_text</span> <span class="o">=</span> <span class="n">logs</span><span class="p">[:</span><span class="mi">1000</span><span class="p">]</span> <span class="k">if</span> <span class="n">logs</span> <span class="k">else</span> <span class="s">"No logs available"</span>
    <span class="n">previous_logs_text</span> <span class="o">=</span> <span class="n">previous_logs</span><span class="p">[:</span><span class="mi">800</span><span class="p">]</span> <span class="k">if</span> <span class="n">previous_logs</span> <span class="k">else</span> <span class="s">"No previous logs"</span>
    
    <span class="c1"># Format events
</span>    <span class="n">events_text</span> <span class="o">=</span> <span class="s">""</span>
    <span class="k">for</span> <span class="n">event</span> <span class="ow">in</span> <span class="n">events</span><span class="p">:</span>
        <span class="n">events_text</span> <span class="o">+=</span> <span class="sa">f</span><span class="s">"• **</span><span class="si">{</span><span class="n">event</span><span class="p">.</span><span class="n">reason</span><span class="si">}</span><span class="s">**: </span><span class="si">{</span><span class="n">event</span><span class="p">.</span><span class="n">message</span><span class="si">}</span><span class="s"> (x</span><span class="si">{</span><span class="n">event</span><span class="p">.</span><span class="n">count</span><span class="si">}</span><span class="s">)</span><span class="se">\n</span><span class="s">"</span>
    
    <span class="n">events_text</span> <span class="o">=</span> <span class="n">events_text</span> <span class="ow">or</span> <span class="s">"No recent events"</span>
    
    <span class="n">embed</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s">"title"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"Pod Restart: </span><span class="si">{</span><span class="n">pod_name</span><span class="si">}</span><span class="s">"</span><span class="p">,</span>
        <span class="s">"description"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"Pod restarted </span><span class="si">{</span><span class="n">restart_count</span><span class="si">}</span><span class="s"> times in the last 5 minutes"</span><span class="p">,</span>
        <span class="s">"color"</span><span class="p">:</span> <span class="mi">15158332</span><span class="p">,</span>  <span class="c1"># Red
</span>        <span class="s">"fields"</span><span class="p">:</span> <span class="p">[</span>
            <span class="p">{</span>
                <span class="s">"name"</span><span class="p">:</span> <span class="s">"Pod"</span><span class="p">,</span>
                <span class="s">"value"</span><span class="p">:</span> <span class="n">pod_name</span><span class="p">,</span>
                <span class="s">"inline"</span><span class="p">:</span> <span class="bp">True</span>
            <span class="p">},</span>
            <span class="p">{</span>
                <span class="s">"name"</span><span class="p">:</span> <span class="s">"Namespace"</span><span class="p">,</span>
                <span class="s">"value"</span><span class="p">:</span> <span class="n">namespace</span><span class="p">,</span>
                <span class="s">"inline"</span><span class="p">:</span> <span class="bp">True</span>
            <span class="p">},</span>
            <span class="p">{</span>
                <span class="s">"name"</span><span class="p">:</span> <span class="s">"Restart Count (5m)"</span><span class="p">,</span>
                <span class="s">"value"</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">restart_count</span><span class="p">),</span>
                <span class="s">"inline"</span><span class="p">:</span> <span class="bp">True</span>
            <span class="p">},</span>
            <span class="p">{</span>
                <span class="s">"name"</span><span class="p">:</span> <span class="s">"Recent Events"</span><span class="p">,</span>
                <span class="s">"value"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"```</span><span class="si">{</span><span class="n">events_text</span><span class="si">}</span><span class="s">```"</span><span class="p">,</span>
                <span class="s">"inline"</span><span class="p">:</span> <span class="bp">False</span>
            <span class="p">},</span>
            <span class="p">{</span>
                <span class="s">"name"</span><span class="p">:</span> <span class="s">"Current Logs (Last 30 lines)"</span><span class="p">,</span>
                <span class="s">"value"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"```</span><span class="si">{</span><span class="n">logs_text</span><span class="si">}</span><span class="s">```"</span><span class="p">,</span>
                <span class="s">"inline"</span><span class="p">:</span> <span class="bp">False</span>
            <span class="p">}</span>
        <span class="p">],</span>
        <span class="s">"timestamp"</span><span class="p">:</span> <span class="n">datetime</span><span class="p">.</span><span class="n">utcnow</span><span class="p">().</span><span class="n">isoformat</span><span class="p">()</span>
    <span class="p">}</span>
    
    <span class="k">if</span> <span class="n">previous_logs</span><span class="p">:</span>
        <span class="n">embed</span><span class="p">[</span><span class="s">"fields"</span><span class="p">].</span><span class="n">append</span><span class="p">({</span>
            <span class="s">"name"</span><span class="p">:</span> <span class="s">"Previous Logs (Before Restart)"</span><span class="p">,</span>
            <span class="s">"value"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"```</span><span class="si">{</span><span class="n">previous_logs_text</span><span class="si">}</span><span class="s">```"</span><span class="p">,</span>
            <span class="s">"inline"</span><span class="p">:</span> <span class="bp">False</span>
        <span class="p">})</span>
    
    <span class="k">return</span> <span class="p">{</span>
        <span class="s">"embeds"</span><span class="p">:</span> <span class="p">[</span><span class="n">embed</span><span class="p">]</span>
    <span class="p">}</span>

<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">'__main__'</span><span class="p">:</span>
    <span class="n">app</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s">'0.0.0.0'</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">5000</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="deploy-webhook-receiver-as-kubernetes-service">Deploy Webhook Receiver as Kubernetes Service</h3>

<p>Create a deployment manifest for the webhook receiver:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ServiceAccount</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">alert-webhook</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">monitoring</span>


<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">rbac.authorization.k8s.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ClusterRole</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">alert-webhook</span>
<span class="na">rules</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">apiGroups</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">"</span><span class="pi">]</span>
  <span class="na">resources</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">events"</span><span class="pi">]</span>
  <span class="na">verbs</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">get"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">list"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">watch"</span><span class="pi">]</span>
<span class="pi">-</span> <span class="na">apiGroups</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">"</span><span class="pi">]</span>
  <span class="na">resources</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">pods"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">pods/log"</span><span class="pi">]</span>
  <span class="na">verbs</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">get"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">list"</span><span class="pi">]</span>


<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">rbac.authorization.k8s.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ClusterRoleBinding</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">alert-webhook</span>
<span class="na">roleRef</span><span class="pi">:</span>
  <span class="na">apiGroup</span><span class="pi">:</span> <span class="s">rbac.authorization.k8s.io</span>
  <span class="na">kind</span><span class="pi">:</span> <span class="s">ClusterRole</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">alert-webhook</span>
<span class="na">subjects</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">kind</span><span class="pi">:</span> <span class="s">ServiceAccount</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">alert-webhook</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">monitoring</span>


<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">alert-webhook</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">monitoring</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">replicas</span><span class="pi">:</span> <span class="m">2</span>
  <span class="na">selector</span><span class="pi">:</span>
    <span class="na">matchLabels</span><span class="pi">:</span>
      <span class="na">app</span><span class="pi">:</span> <span class="s">alert-webhook</span>
  <span class="na">template</span><span class="pi">:</span>
    <span class="na">metadata</span><span class="pi">:</span>
      <span class="na">labels</span><span class="pi">:</span>
        <span class="na">app</span><span class="pi">:</span> <span class="s">alert-webhook</span>
    <span class="na">spec</span><span class="pi">:</span>
      <span class="na">serviceAccountName</span><span class="pi">:</span> <span class="s">alert-webhook</span>
      <span class="na">containers</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">webhook</span>
        <span class="na">image</span><span class="pi">:</span> <span class="s">python:3.11-slim</span>
        <span class="na">command</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">sh"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">-c"</span><span class="pi">]</span>
        <span class="na">args</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="pi">|</span>
            <span class="s">pip install flask requests kubernetes &amp;&amp;</span>
            <span class="s">python /app/webhook.py</span>
        <span class="na">volumeMounts</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">webhook-code</span>
          <span class="na">mountPath</span><span class="pi">:</span> <span class="s">/app</span>
        <span class="na">ports</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">containerPort</span><span class="pi">:</span> <span class="m">5000</span>
        <span class="na">env</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">SLACK_WEBHOOK_URL</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">secretKeyRef</span><span class="pi">:</span>
              <span class="na">name</span><span class="pi">:</span> <span class="s">alert-webhooks</span>
              <span class="na">key</span><span class="pi">:</span> <span class="s">slack-url</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">DISCORD_WEBHOOK_URL</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">secretKeyRef</span><span class="pi">:</span>
              <span class="na">name</span><span class="pi">:</span> <span class="s">alert-webhooks</span>
              <span class="na">key</span><span class="pi">:</span> <span class="s">discord-url</span>
      <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">webhook-code</span>
        <span class="na">configMap</span><span class="pi">:</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">alert-webhook-code</span>


<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ConfigMap</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">alert-webhook-code</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">monitoring</span>
<span class="na">data</span><span class="pi">:</span>
  <span class="na">webhook.py</span><span class="pi">:</span> <span class="pi">|</span>
    <span class="s"># [Paste the Flask code from above]</span>


<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Service</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">alert-webhook</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">monitoring</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">selector</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">alert-webhook</span>
  <span class="na">ports</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">port</span><span class="pi">:</span> <span class="m">80</span>
    <span class="na">targetPort</span><span class="pi">:</span> <span class="m">5000</span>
  <span class="na">type</span><span class="pi">:</span> <span class="s">ClusterIP</span>


<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Secret</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">alert-webhooks</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">monitoring</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">Opaque</span>
<span class="na">stringData</span><span class="pi">:</span>
  <span class="na">slack-url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"</span>
  <span class="na">discord-url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://discord.com/api/webhooks/YOUR/DISCORD/WEBHOOK"</span>
</code></pre></div></div>

<p>Deploy:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl apply <span class="nt">-f</span> alert-webhook-deployment.yaml
</code></pre></div></div>

<h2 id="part-6-configure-grafana-webhook-contact-point">Part 6: Configure Grafana Webhook Contact Point</h2>

<ol>
  <li><strong>Login to Grafana</strong></li>
  <li>Navigate to <strong>Alerting → Contact Points → New Contact Point</strong></li>
  <li>Select <strong>Webhook</strong></li>
  <li>
    <p>Configure:</p>

    <ul>
      <li><strong>Name</strong>: <code class="language-plaintext highlighter-rouge">pod-restart-webhook</code></li>
      <li><strong>URL</strong>: <code class="language-plaintext highlighter-rouge">http://alert-webhook.monitoring.svc.cluster.local/alert?target=slack</code></li>
      <li><strong>HTTP Method</strong>: POST</li>
    </ul>
  </li>
  <li>Test and save</li>
</ol>

<h2 id="part-7-advanced-webhook-customization">Part 7: Advanced Webhook Customization</h2>

<h3 id="custom-payload-template">Custom Payload Template</h3>

<p>Use Grafana’s custom payload feature for fine-grained control:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
  <span class="s">"alert_name"</span><span class="o">:</span> <span class="s">""</span><span class="p">,</span>
  <span class="s">"status"</span><span class="o">:</span> <span class="s">""</span><span class="p">,</span>
  <span class="s">"pod"</span><span class="o">:</span> <span class="s">""</span><span class="p">,</span>
  <span class="s">"namespace"</span><span class="o">:</span> <span class="s">""</span><span class="p">,</span>
  <span class="s">"grafana_url"</span><span class="o">:</span> <span class="s">""</span><span class="p">,</span>
  <span class="s">"timestamp"</span><span class="o">:</span> <span class="s">""</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="integration-with-loki-logs-in-alerts">Integration with Loki Logs in Alerts</h3>

<p>Embed log queries directly in alert annotations:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">annotations</span><span class="pi">:</span>
  <span class="na">logs_link</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://grafana.example.com/explore?left={</span><span class="se">\"</span><span class="s">datasource</span><span class="se">\"</span><span class="s">:</span><span class="se">\"</span><span class="s">Loki</span><span class="se">\"</span><span class="s">,</span><span class="se">\"</span><span class="s">queries</span><span class="se">\"</span><span class="s">:[{</span><span class="se">\"</span><span class="s">refId</span><span class="se">\"</span><span class="s">:</span><span class="se">\"</span><span class="s">A</span><span class="se">\"</span><span class="s">,</span><span class="se">\"</span><span class="s">expr</span><span class="se">\"</span><span class="s">:</span><span class="se">\"</span><span class="s">{namespace=</span><span class="se">\\\"\\\"</span><span class="s">,pod=</span><span class="se">\\\"\\\"</span><span class="s">}</span><span class="se">\"</span><span class="s">}]}"</span>
</code></pre></div></div>

<h2 id="part-9-production-best-practices">Part 9: Production Best Practices</h2>

<h3 id="1-log-retention">1. Log Retention</h3>

<p>Configure Loki retention policies to balance cost and compliance:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">ingester</span><span class="pi">:</span>
  <span class="na">chunk_retain_period</span><span class="pi">:</span> <span class="s">1m</span>
  <span class="na">max_chunk_age</span><span class="pi">:</span> <span class="s">2h</span>

<span class="na">schema_config</span><span class="pi">:</span>
  <span class="na">configs</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">from</span><span class="pi">:</span> <span class="s">2020-10-24</span>
      <span class="na">store</span><span class="pi">:</span> <span class="s">boltdb-shipper</span>
      <span class="na">object_store</span><span class="pi">:</span> <span class="s">filesystem</span>
      <span class="na">schema</span><span class="pi">:</span> <span class="s">v11</span>
      <span class="na">index</span><span class="pi">:</span>
        <span class="na">prefix</span><span class="pi">:</span> <span class="s">index_</span>
        <span class="na">period</span><span class="pi">:</span> <span class="s">24h</span>

<span class="na">limits_config</span><span class="pi">:</span>
  <span class="na">retention_period</span><span class="pi">:</span> <span class="s">720h</span>  <span class="c1"># 30 days</span>
</code></pre></div></div>

<h3 id="2-alert-routing-and-grouping">2. Alert Routing and Grouping</h3>

<p>Define notification policies to avoid alert fatigue:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ConfigMap</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">grafana-alert-notification-policy</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">monitoring</span>
<span class="na">data</span><span class="pi">:</span>
  <span class="na">notification-policy.yaml</span><span class="pi">:</span> <span class="pi">|</span>
    <span class="s">receiver: 'slack-critical'</span>
    <span class="s">group_by: ['namespace', 'pod']</span>
    <span class="s">group_wait: 10s</span>
    <span class="s">group_interval: 1m</span>
    <span class="s">repeat_interval: 4h</span>
    <span class="s">routes:</span>
    <span class="s">- receiver: 'slack-prod'</span>
      <span class="s">match:</span>
        <span class="s">environment: 'production'</span>
      <span class="s">group_wait: 5s</span>
      <span class="s">repeat_interval: 2h</span>
    <span class="s">- receiver: 'slack-staging'</span>
      <span class="s">match:</span>
        <span class="s">environment: 'staging'</span>
</code></pre></div></div>

<h3 id="3-rate-limiting">3. Rate Limiting</h3>

<p>Prevent webhook endpoint overload:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">flask_limiter</span> <span class="kn">import</span> <span class="n">Limiter</span>
<span class="kn">from</span> <span class="nn">flask_limiter.util</span> <span class="kn">import</span> <span class="n">get_remote_address</span>

<span class="n">limiter</span> <span class="o">=</span> <span class="n">Limiter</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">key_func</span><span class="o">=</span><span class="n">get_remote_address</span><span class="p">)</span>

<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/alert'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'POST'</span><span class="p">])</span>
<span class="o">@</span><span class="n">limiter</span><span class="p">.</span><span class="n">limit</span><span class="p">(</span><span class="s">"100 per minute"</span><span class="p">)</span>  <span class="c1"># Max 100 requests/minute
</span><span class="k">def</span> <span class="nf">handle_alert</span><span class="p">():</span>
    <span class="c1"># ...
</span></code></pre></div></div>

<h3 id="4-monitoring-the-webhook-receiver">4. Monitoring the Webhook Receiver</h3>

<p>Add Prometheus metrics to track webhook performance:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">prometheus_client</span> <span class="kn">import</span> <span class="n">Counter</span><span class="p">,</span> <span class="n">Histogram</span>

<span class="n">webhook_requests</span> <span class="o">=</span> <span class="n">Counter</span><span class="p">(</span>
    <span class="s">'webhook_requests_total'</span><span class="p">,</span>
    <span class="s">'Total webhook requests'</span><span class="p">,</span>
    <span class="p">[</span><span class="s">'status'</span><span class="p">]</span>
<span class="p">)</span>

<span class="n">webhook_duration</span> <span class="o">=</span> <span class="n">Histogram</span><span class="p">(</span>
    <span class="s">'webhook_duration_seconds'</span><span class="p">,</span>
    <span class="s">'Webhook processing duration'</span>
<span class="p">)</span>

<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/alert'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'POST'</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">handle_alert</span><span class="p">():</span>
    <span class="k">with</span> <span class="n">webhook_duration</span><span class="p">.</span><span class="n">time</span><span class="p">():</span>
        <span class="c1"># Handle alert
</span>        <span class="n">webhook_requests</span><span class="p">.</span><span class="n">labels</span><span class="p">(</span><span class="n">status</span><span class="o">=</span><span class="s">'success'</span><span class="p">).</span><span class="n">inc</span><span class="p">()</span>
</code></pre></div></div>

<h2 id="part-10-other-scenarios">Part 10: Other Scenarios</h2>

<h3 id="scenario-1-crashloopbackoff-detection">Scenario 1: CrashLoopBackOff Detection</h3>

<p>Combine pod restart metrics with event data to detect crash loops:</p>

<pre><code class="language-promql"># Alert when pod restarts exceed 5 in 10 minutes
rate(kube_pod_container_status_restarts_total[10m]) &gt; 0.5
</code></pre>

<p>Query related events:</p>

<pre><code class="language-promql">kube_event_count{
  involved_object_kind="Pod",
  reason=~"BackOff|Failed"
}
</code></pre>

<h3 id="scenario-2-multi-container-pod-restarts">Scenario 2: Multi-Container Pod Restarts</h3>

<p>Identify which container in a multi-container pod is restarting:</p>

<pre><code class="language-promql">kube_pod_container_status_restarts_total{container!=""}
</code></pre>

<p>Update webhook to fetch logs per-container:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Get all containers in the pod
</span><span class="n">pod</span> <span class="o">=</span> <span class="n">v1</span><span class="p">.</span><span class="n">read_namespaced_pod</span><span class="p">(</span><span class="n">pod_name</span><span class="p">,</span> <span class="n">namespace</span><span class="p">)</span>
<span class="n">containers</span> <span class="o">=</span> <span class="p">[</span><span class="n">c</span><span class="p">.</span><span class="n">name</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">pod</span><span class="p">.</span><span class="n">spec</span><span class="p">.</span><span class="n">containers</span><span class="p">]</span>

<span class="c1"># Fetch logs for each
</span><span class="k">for</span> <span class="n">container_name</span> <span class="ow">in</span> <span class="n">containers</span><span class="p">:</span>
    <span class="n">logs</span> <span class="o">=</span> <span class="n">get_pod_logs</span><span class="p">(</span><span class="n">namespace</span><span class="p">,</span> <span class="n">pod_name</span><span class="p">,</span> <span class="n">container_name</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="scenario-3-cross-namespace-pod-restart-correlation">Scenario 3: Cross-Namespace Pod Restart Correlation</h3>

<p>Alert on spikes across multiple namespaces:</p>

<pre><code class="language-promql">sum by (namespace) (
  rate(kube_pod_container_status_restarts_total[5m])
) &gt; 0.5
</code></pre>

<h2 id="conclusion">Conclusion</h2>

<p>By combining Prometheus metrics, Kubernetes events, and application logs within Grafana alerts, you create a powerful incident response system that surfaces context-rich notifications. This setup enables ops teams to move from reactive firefighting to proactive, informed incident triage.</p>

<p><strong>Key Takeaways:</strong></p>

<ul>
  <li>Use <strong>event-exporter</strong> to bridge ephemeral Kubernetes events to Prometheus</li>
  <li>Deploy <strong>Loki</strong> to centralize and query pod logs at scale</li>
  <li>Build custom webhook receivers to enrich alerts with real-time cluster state</li>
  <li>Implement rate limiting and monitoring on webhook endpoints for production reliability</li>
  <li>Use structured logging and label-based routing to reduce alert fatigue</li>
</ul>]]></content><author><name>Mustaque Ahmed</name><email>amustaque97@gmail.com</email></author><category term="infra" /><category term="debugging" /><category term="kubernetes" /><category term="alerts" /><summary type="html"><![CDATA[How to Send Alerts to Slack/Discord When a Kubernetes Pod Restarts Using Grafana including Kubernetes Events and Recent Pod Logs]]></summary></entry><entry><title type="html">Kubernetes Sidecars Stop Losing Logs When Your App Crashes</title><link href="https://amustaque97.github.io/stop-losing-logs-when-app-crashes/" rel="alternate" type="text/html" title="Kubernetes Sidecars Stop Losing Logs When Your App Crashes" /><published>2025-11-02T00:00:00+05:30</published><updated>2025-11-02T00:00:00+05:30</updated><id>https://amustaque97.github.io/stop-losing-logs-when-app-crashes</id><content type="html" xml:base="https://amustaque97.github.io/stop-losing-logs-when-app-crashes/"><![CDATA[<h3 id="introduction">Introduction</h3>
<p>You get paged at 2 AM. Your API is down. You SSH into the container to check the logs—your first instinct, right? You navigate to /var/log/app.log and find… nothing. Empty. The container restarted and took your logs with it.</p>

<p>This is a problem every DevOps engineer faces. When containers crash, they take their logs with them. We’re left debugging blind, guessing what went wrong, and wasting hours on incident response.</p>

<p>But what if logs could survive container crashes? What if they were stored separately, independently, and kept safe even when your application fails?</p>

<p>That’s exactly what Kubernetes sidecars can do.</p>

<h2 id="whats-the-problem-were-solving">What’s the Problem We’re Solving?</h2>

<h3 id="the-lost-log-nightmare">The Lost Log Nightmare</h3>

<p>Traditional containerized applications store logs inside the container. When the container crashes or restarts, those logs disappear. Here’s what happens:</p>

<ol>
  <li><strong>Application crashes</strong> → Container terminates</li>
  <li><strong>All logs stored in the container are lost</strong> → Data gone forever</li>
  <li><strong>New container spins up</strong> → Fresh start, no history</li>
  <li><strong>Team spends hours reconstructing what happened</strong> → Lost productivity</li>
  <li><strong>Root cause remains unclear</strong> → Problem might happen again</li>
</ol>

<p>We’ve all been there. The post-incident review is painful: “We don’t know exactly what failed. The logs were already gone.”</p>

<h3 id="additional-challenges">Additional Challenges</h3>

<p>Beyond lost logs, teams face other operational headaches:</p>

<ul>
  <li><strong>Logging is embedded in application code</strong> → Every team implements it differently</li>
  <li><strong>No centralized control</strong> → Can’t enforce logging standards</li>
  <li><strong>Scattered log destinations</strong> → Some go to files, some to stdout, some to Datadog directly</li>
  <li><strong>Security concerns</strong> → Sensitive data (passwords, API keys, credit cards) get logged accidentally</li>
  <li><strong>No audit trail for compliance</strong> → Regulations require logs that survive crashes</li>
</ul>

<h2 id="what-is-a-kubernetes-sidecar">What is a Kubernetes Sidecar?</h2>

<p>A <strong>sidecar</strong> is a small, lightweight container that runs alongside your main application container in the same Kubernetes Pod.</p>

<h3 id="key-characteristics">Key Characteristics</h3>

<p><strong>Shared Pod Network:</strong>
Both containers run in the same Pod and share the same network namespace. They can communicate via localhost.</p>

<p><strong>Shared Storage:</strong>
Sidecars can mount the same volumes as the main application. This allows them to read files, share state, or coordinate work.</p>

<p><strong>Independent Lifecycle:</strong>
If the main app crashes, the sidecar keeps running. If the sidecar crashes, it doesn’t directly affect the main app (though functionality might be lost).</p>

<p><strong>Lightweight:</strong>
Sidecars are designed to be minimal and resource-efficient. They shouldn’t compete with your main application for resources.</p>

<h3 id="visual-architecture">Visual Architecture</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────────────────┐
│         Kubernetes Pod              │
├─────────────────────────────────────┤
│                                     │
│  ┌──────────────┐  ┌─────────────┐ │
│  │   Main App   │  │   Sidecar   │ │
│  │ (Your API)   │  │ (Log Agent) │ │
│  │              │  │             │ │
│  └──────────────┘  └─────────────┘ │
│       │                    │        │
│       └────────┬───────────┘        │
│                │                    │
│         ┌──────▼──────┐             │
│         │ Shared Volume│             │
│         │  /var/log   │             │
│         └─────────────┘             │
│                                     │
└─────────────────────────────────────┘
</code></pre></div></div>

<h2 id="the-solution-logging-sidecar-pattern">The Solution: Logging Sidecar Pattern</h2>

<h3 id="how-it-works">How It Works</h3>

<p>The logging sidecar pattern is elegantly simple:</p>

<ol>
  <li><strong>Your main app writes logs</strong> to a file on a shared volume (<code class="language-plaintext highlighter-rouge">/var/log/app.log</code>)</li>
  <li><strong>The sidecar container tails that file</strong> continuously</li>
  <li><strong>The sidecar forwards logs</strong> to a central storage system (Elasticsearch, CloudWatch, Datadog, etc.)</li>
  <li><strong>When the main app crashes</strong>, the sidecar keeps running and keeps sending logs</li>
  <li><strong>When a new instance of the app starts</strong>, logs are already waiting in central storage</li>
  <li><strong>You can debug the crash</strong> with complete log history</li>
</ol>

<h3 id="the-kubernetes-yaml">The Kubernetes YAML</h3>

<p>Here’s the complete implementation:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">api-service</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">api</span>
    <span class="na">version</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">containers</span><span class="pi">:</span>
  <span class="c1"># The main application container</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">app</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">mycompany/api:v2.1.0</span>
    <span class="na">ports</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">containerPort</span><span class="pi">:</span> <span class="m">8080</span>
    <span class="na">volumeMounts</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">logs</span>
      <span class="na">mountPath</span><span class="pi">:</span> <span class="s">/var/log</span>
    <span class="na">env</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">LOG_PATH</span>
      <span class="na">value</span><span class="pi">:</span> <span class="s">/var/log/app.log</span>
    <span class="na">resources</span><span class="pi">:</span>
      <span class="na">requests</span><span class="pi">:</span>
        <span class="na">memory</span><span class="pi">:</span> <span class="s2">"</span><span class="s">256Mi"</span>
        <span class="na">cpu</span><span class="pi">:</span> <span class="s2">"</span><span class="s">500m"</span>
      <span class="na">limits</span><span class="pi">:</span>
        <span class="na">memory</span><span class="pi">:</span> <span class="s2">"</span><span class="s">512Mi"</span>
        <span class="na">cpu</span><span class="pi">:</span> <span class="s2">"</span><span class="s">1000m"</span>

  <span class="c1"># The sidecar that collects and ships logs</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">log-collector</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">fluent/fluent-bit:2.1.0</span>
    <span class="na">volumeMounts</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">logs</span>
      <span class="na">mountPath</span><span class="pi">:</span> <span class="s">/var/log</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">fluent-config</span>
      <span class="na">mountPath</span><span class="pi">:</span> <span class="s">/fluent-bit/etc/</span>
    <span class="na">resources</span><span class="pi">:</span>
      <span class="na">requests</span><span class="pi">:</span>
        <span class="na">memory</span><span class="pi">:</span> <span class="s2">"</span><span class="s">64Mi"</span>
        <span class="na">cpu</span><span class="pi">:</span> <span class="s2">"</span><span class="s">100m"</span>
      <span class="na">limits</span><span class="pi">:</span>
        <span class="na">memory</span><span class="pi">:</span> <span class="s2">"</span><span class="s">128Mi"</span>
        <span class="na">cpu</span><span class="pi">:</span> <span class="s2">"</span><span class="s">250m"</span>

  <span class="na">volumes</span><span class="pi">:</span>
  <span class="c1"># Shared storage for logs</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">logs</span>
    <span class="na">emptyDir</span><span class="pi">:</span> <span class="pi">{}</span>
  <span class="c1"># ConfigMap with Fluent Bit configuration</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">fluent-config</span>
    <span class="na">configMap</span><span class="pi">:</span>
      <span class="na">name</span><span class="pi">:</span> <span class="s">fluent-bit-config</span>
</code></pre></div></div>

<h3 id="fluent-bit-configuration">Fluent Bit Configuration</h3>

<p>The sidecar uses Fluent Bit to parse and forward logs. Here’s a simple configuration:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[SERVICE]</span>
    <span class="err">Flush</span>         <span class="err">5</span>
    <span class="err">Daemon</span>        <span class="err">off</span>
    <span class="err">Log_Level</span>     <span class="err">info</span>

<span class="nn">[INPUT]</span>
    <span class="err">Name</span>              <span class="err">tail</span>
    <span class="err">Path</span>              <span class="err">/var/log/app.log</span>
    <span class="err">Parser</span>            <span class="err">docker</span>
    <span class="err">Tag</span>               <span class="err">app.*</span>
    <span class="err">Refresh_Interval</span>  <span class="err">5</span>

<span class="nn">[OUTPUT]</span>
    <span class="err">Name</span>   <span class="err">stdout</span>
    <span class="err">Match</span>  <span class="err">*</span>

<span class="nn">[OUTPUT]</span>
    <span class="err">Name</span>                <span class="err">stackdriver</span>
    <span class="err">Match</span>               <span class="err">*</span>
    <span class="err">google_service_credentials</span> <span class="err">/var/secrets/google/key.json</span>
    <span class="err">project_id</span>          <span class="err">my-gcp-project</span>
    <span class="err">resource_type</span>       <span class="err">k8s_container</span>
</code></pre></div></div>

<p>This configuration tells Fluent Bit to:</p>
<ul>
  <li>Tail the app log file</li>
  <li>Parse Docker JSON logs</li>
  <li>Output logs to both stdout (for debugging) and Google Cloud Logging</li>
</ul>

<h2 id="why-fluent-bit">Why Fluent Bit?</h2>

<p>When you’re choosing a logging tool for sidecars, you have several options. Here’s why Fluent Bit is the industry standard:</p>

<h3 id="memory-efficiency">Memory Efficiency</h3>

<p>Fluent Bit uses approximately <strong>650 KB of memory</strong>. Compare this to alternatives:</p>

<ul>
  <li><strong>Logstash</strong>: 500 MB+ (Java-based, heavyweight)</li>
  <li><strong>Fluentd</strong>: 40 MB (Ruby-based, heavier)</li>
  <li><strong>Vector</strong>: 10 MB (Rust-based, modern)</li>
</ul>

<p>In a sidecar, every megabyte matters. Your sidecar shouldn’t starve your main application for resources.</p>

<h3 id="kubernetes-native">Kubernetes Native</h3>

<p>Fluent Bit is built specifically for cloud-native environments:</p>

<ul>
  <li>Automatic Pod metadata enrichment (namespace, pod name, labels)</li>
  <li>Native support for Kubernetes logging formats</li>
  <li>Official CNCF (Cloud Native Computing Foundation) backing</li>
  <li>Works seamlessly with container runtimes</li>
</ul>

<h3 id="universal-compatibility">Universal Compatibility</h3>

<p>Fluent Bit supports 40+ output destinations:</p>

<ul>
  <li><strong>Log Management</strong>: Elasticsearch, Splunk, Datadog, New Relic, Sumo Logic</li>
  <li><strong>Cloud Providers</strong>: CloudWatch (AWS), Cloud Logging (GCP), Log Analytics (Azure)</li>
  <li><strong>Message Queues</strong>: Kafka, RabbitMQ, MQTT</li>
  <li><strong>Object Storage</strong>: S3, GCS</li>
  <li><strong>Monitoring</strong>: Prometheus, Grafana Loki</li>
  <li><strong>Custom</strong>: HTTP, TCP, stdout</li>
</ul>

<p>One logging sidecar image works with any backend. No need to rebuild containers for different environments.</p>

<h3 id="production-ready">Production Ready</h3>

<p>Fluent Bit is battle-tested:</p>

<ul>
  <li>Used by thousands of companies in production</li>
  <li>Maintained by the CNCF</li>
  <li>Active community and frequent updates</li>
  <li>Excellent documentation</li>
</ul>

<h2 id="when-to-use-alternatives">When to Use Alternatives</h2>

<p>While Fluent Bit is the default choice, sometimes other tools make sense:</p>

<h3 id="filebeat">Filebeat</h3>

<p><strong>Use if:</strong> You’re already deeply invested in the Elastic Stack (Elasticsearch, Logstash, Kibana)</p>

<p><strong>Advantages:</strong></p>
<ul>
  <li>Optimized for Elastic</li>
  <li>Smaller footprint than Logstash (~20MB)</li>
  <li>Native Elasticsearch integration</li>
</ul>

<p><strong>Disadvantages:</strong></p>
<ul>
  <li>Limited to Elastic ecosystem</li>
  <li>Less flexible for multi-destination setups</li>
</ul>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">log-collector</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">docker.elastic.co/beats/filebeat:8.11.0</span>
</code></pre></div></div>

<h3 id="vector">Vector</h3>

<p><strong>Use if:</strong> You need maximum performance and are handling millions of events per second</p>

<p><strong>Advantages:</strong></p>
<ul>
  <li>Written in Rust (extremely fast)</li>
  <li>Only ~10MB memory</li>
  <li>Modern architecture</li>
  <li>Growing ecosystem</li>
</ul>

<p><strong>Disadvantages:</strong></p>
<ul>
  <li>Newer tool, smaller community</li>
  <li>Less documentation than Fluent Bit</li>
</ul>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">log-collector</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">timberio/vector:0.34.0-alpine</span>
</code></pre></div></div>

<h3 id="fluentd">Fluentd</h3>

<p><strong>Use if:</strong> You need complex log transformations and have specific Fluentd plugins</p>

<p><strong>Advantages:</strong></p>
<ul>
  <li>500+ plugins available</li>
  <li>Ruby ecosystem</li>
  <li>Powerful filtering</li>
</ul>

<p><strong>Disadvantages:</strong></p>
<ul>
  <li>Heavier (~40MB)</li>
  <li>Slower startup time</li>
  <li>More complex to configure</li>
</ul>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">log-collector</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">fluent/fluentd:v1.16-1</span>
</code></pre></div></div>

<h3 id="logstash">Logstash</h3>

<p><strong>Use if:</strong> You’re processing logs through complex pipelines with heavy transformations</p>

<p><strong>Advantages:</strong></p>
<ul>
  <li>Extremely flexible</li>
  <li>Powerful DSL for transformations</li>
</ul>

<p><strong>Disadvantages:</strong></p>
<ul>
  <li>Very heavy (500MB+)</li>
  <li>Slow startup</li>
  <li>Overkill for most sidecar use cases</li>
</ul>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">log-collector</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">docker.elastic.co/logstash/logstash:8.11.0</span>
</code></pre></div></div>

<h3 id="comparison-table">Comparison Table</h3>

<table>
  <thead>
    <tr>
      <th>Tool</th>
      <th>Memory</th>
      <th>Startup</th>
      <th>Best For</th>
      <th>Destinations</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Fluent Bit</strong></td>
      <td>650 KB</td>
      <td>&lt;1s</td>
      <td>General use, Kubernetes</td>
      <td>40+</td>
    </tr>
    <tr>
      <td><strong>Filebeat</strong></td>
      <td>~20 MB</td>
      <td>1-2s</td>
      <td>Elastic Stack</td>
      <td>Elastic</td>
    </tr>
    <tr>
      <td><strong>Vector</strong></td>
      <td>~10 MB</td>
      <td>&lt;1s</td>
      <td>High performance</td>
      <td>30+</td>
    </tr>
    <tr>
      <td><strong>Fluentd</strong></td>
      <td>40 MB</td>
      <td>2-3s</td>
      <td>Complex transforms</td>
      <td>500+</td>
    </tr>
    <tr>
      <td><strong>Logstash</strong></td>
      <td>500 MB+</td>
      <td>5-10s</td>
      <td>Heavy pipelines</td>
      <td>Many</td>
    </tr>
  </tbody>
</table>

<h2 id="real-world-impact">Real-World Impact</h2>

<p>Let’s look at actual metrics from teams that implemented logging sidecars:</p>

<h3 id="before-sidecars">Before Sidecars</h3>

<ul>
  <li><strong>Lost logs on crashes</strong>: Yes, every time</li>
  <li><strong>MTTR (Mean Time To Recovery)</strong>: 90-180 minutes</li>
  <li><strong>Debugging success rate</strong>: 40% (often can’t identify root cause)</li>
  <li><strong>Team frustration</strong>: High (endless guessing)</li>
  <li><strong>Compliance audit results</strong>: Failed (no persistent logs)</li>
</ul>

<h3 id="after-sidecars">After Sidecars</h3>

<ul>
  <li><strong>Lost logs on crashes</strong>: Zero</li>
  <li><strong>MTTR</strong>: 15-30 minutes (immediate log access)</li>
  <li><strong>Debugging success rate</strong>: 99%+ (complete visibility)</li>
  <li><strong>Team frustration</strong>: Low (root causes are obvious)</li>
  <li><strong>Compliance audit results</strong>: Passed (logs survive crashes)</li>
</ul>

<h3 id="the-numbers">The Numbers</h3>

<ul>
  <li><strong>90% reduction</strong> in time spent debugging incidents</li>
  <li><strong>75% reduction</strong> in incident duration</li>
  <li><strong>100% log capture rate</strong> for application crashes</li>
  <li><strong>Zero</strong> additional app code changes required</li>
</ul>

<h2 id="best-practices-for-sidecar-logging">Best Practices for Sidecar Logging</h2>

<h3 id="1-set-resource-limits">1. Set Resource Limits</h3>

<p>Always specify resource requests and limits for sidecars:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">resources</span><span class="pi">:</span>
  <span class="na">requests</span><span class="pi">:</span>
    <span class="na">memory</span><span class="pi">:</span> <span class="s2">"</span><span class="s">64Mi"</span>
    <span class="na">cpu</span><span class="pi">:</span> <span class="s2">"</span><span class="s">100m"</span>
  <span class="na">limits</span><span class="pi">:</span>
    <span class="na">memory</span><span class="pi">:</span> <span class="s2">"</span><span class="s">128Mi"</span>
    <span class="na">cpu</span><span class="pi">:</span> <span class="s2">"</span><span class="s">250m"</span>
</code></pre></div></div>

<p>This prevents sidecars from consuming excessive resources and starving your main app.</p>

<h3 id="2-use-shared-volumes-efficiently">2. Use Shared Volumes Efficiently</h3>

<p>Mount volumes at specific paths, not the entire filesystem:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">volumeMounts</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">logs</span>
  <span class="na">mountPath</span><span class="pi">:</span> <span class="s">/var/log</span>
</code></pre></div></div>

<p>Not:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">volumeMounts</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">data</span>
  <span class="na">mountPath</span><span class="pi">:</span> <span class="s">/</span>
</code></pre></div></div>

<h3 id="3-configure-log-rotation">3. Configure Log Rotation</h3>

<p>Prevent log files from growing unbounded:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[INPUT]</span>
    <span class="err">Name</span>              <span class="err">tail</span>
    <span class="err">Path</span>              <span class="err">/var/log/app.log</span>
    <span class="err">Rotate_Wait</span>       <span class="err">30</span>
    <span class="err">Skip_Long_Lines</span>   <span class="err">On</span>
    <span class="err">Mem_Buf_Limit</span>     <span class="err">5MB</span>
</code></pre></div></div>

<h3 id="4-add-metadata-enrichment">4. Add Metadata Enrichment</h3>

<p>Enhance logs with Kubernetes metadata:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[FILTER]</span>
    <span class="err">Name</span>                <span class="err">kubernetes</span>
    <span class="err">Match</span>               <span class="err">*</span>
    <span class="err">Kube_URL</span>            <span class="err">https://kubernetes.default.svc:443</span>
    <span class="err">Kube_CA_File</span>        <span class="err">/var/run/secrets/kubernetes.io/serviceaccount/ca.crt</span>
    <span class="err">Kube_Token_File</span>     <span class="err">/var/run/secrets/kubernetes.io/serviceaccount/token</span>
    <span class="err">Merge_Log</span>           <span class="err">On</span>
    <span class="err">Keep_Log</span>            <span class="err">Off</span>
</code></pre></div></div>

<h3 id="5-use-a-dedicated-service-account">5. Use a Dedicated Service Account</h3>

<p>Create a service account with appropriate permissions:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ServiceAccount</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">log-collector-sa</span>

<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">rbac.authorization.k8s.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ClusterRole</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">log-collector-role</span>
<span class="na">rules</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">apiGroups</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">"</span><span class="pi">]</span>
  <span class="na">resources</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">pods"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">namespaces"</span><span class="pi">]</span>
  <span class="na">verbs</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">get"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">list"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">watch"</span><span class="pi">]</span>

<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">rbac.authorization.k8s.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ClusterRoleBinding</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">log-collector-binding</span>
<span class="na">roleRef</span><span class="pi">:</span>
  <span class="na">apiGroup</span><span class="pi">:</span> <span class="s">rbac.authorization.k8s.io</span>
  <span class="na">kind</span><span class="pi">:</span> <span class="s">ClusterRole</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">log-collector-role</span>
<span class="na">subjects</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">kind</span><span class="pi">:</span> <span class="s">ServiceAccount</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">log-collector-sa</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">default</span>
</code></pre></div></div>

<h2 id="beyond-logging-other-sidecar-use-cases">Beyond Logging: Other Sidecar Use Cases</h2>

<p>While this guide focuses on logging, sidecars are useful for many operational concerns:</p>

<h3 id="metrics-collection">Metrics Collection</h3>

<p>Deploy a Prometheus metrics exporter as a sidecar:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">metrics-collector</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">prom/node-exporter:latest</span>
</code></pre></div></div>

<h3 id="security--encryption">Security &amp; Encryption</h3>

<p>Run a TLS termination proxy:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">security-proxy</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">envoyproxy/envoy:latest</span>
</code></pre></div></div>

<h3 id="configuration-management">Configuration Management</h3>

<p>Sync configuration from a remote source:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">config-sync</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">nginx:latest</span>  <span class="c1"># or custom git sync image</span>
  <span class="na">volumeMounts</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">config</span>
    <span class="na">mountPath</span><span class="pi">:</span> <span class="s">/etc/config</span>
</code></pre></div></div>

<h3 id="service-mesh-integration">Service Mesh Integration</h3>

<p>Deploy a service mesh sidecar like Envoy or Linkerd:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">linkerd-proxy</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">cr.l5d.io/linkerd/proxy:stable</span>
</code></pre></div></div>

<h3 id="health-monitoring">Health Monitoring</h3>

<p>Run a dedicated health checker:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">health-monitor</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">custom/health-checker:v1</span>
</code></pre></div></div>

<h2 id="deployment-strategies">Deployment Strategies</h2>

<h3 id="manual-deployment">Manual Deployment</h3>

<p>Add the sidecar directly to your Pod manifests:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl apply <span class="nt">-f</span> pod-with-sidecar.yaml
</code></pre></div></div>

<h3 id="using-deployment-templates">Using Deployment Templates</h3>

<p>Define sidecars in Deployment specifications:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">api</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">template</span><span class="pi">:</span>
    <span class="na">spec</span><span class="pi">:</span>
      <span class="na">containers</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">app</span>
        <span class="na">image</span><span class="pi">:</span> <span class="s">myapp:latest</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">log-collector</span>
        <span class="na">image</span><span class="pi">:</span> <span class="s">fluent/fluent-bit:latest</span>
</code></pre></div></div>

<h3 id="automatic-injection-via-webhooks">Automatic Injection via Webhooks</h3>

<p>Use mutating admission webhooks to automatically inject sidecars:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">admissionregistration.k8s.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">MutatingWebhookConfiguration</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">sidecar-injector</span>
<span class="na">webhooks</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">sidecar-injector.example.com</span>
  <span class="na">admissionReviewVersions</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">v1"</span><span class="pi">]</span>
  <span class="na">clientConfig</span><span class="pi">:</span>
    <span class="na">service</span><span class="pi">:</span>
      <span class="na">name</span><span class="pi">:</span> <span class="s">sidecar-injector</span>
      <span class="na">namespace</span><span class="pi">:</span> <span class="s">default</span>
      <span class="na">path</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/mutate"</span>
    <span class="na">caBundle</span><span class="pi">:</span> <span class="s">...</span>
  <span class="na">rules</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">operations</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">CREATE"</span><span class="pi">]</span>
    <span class="na">apiGroups</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">"</span><span class="pi">]</span>
    <span class="na">apiVersions</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">v1"</span><span class="pi">]</span>
    <span class="na">resources</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">pods"</span><span class="pi">]</span>
    <span class="na">scope</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Namespaced"</span>
  <span class="na">failurePolicy</span><span class="pi">:</span> <span class="s">Ignore</span>
  <span class="na">sideEffects</span><span class="pi">:</span> <span class="s">None</span>
</code></pre></div></div>

<p>This allows you to inject sidecars based on Pod labels without modifying application code.</p>

<h3 id="using-service-mesh">Using Service Mesh</h3>

<p>Service meshes like Istio automatically inject sidecars:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">sidecar.istio.io/inject</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>
</code></pre></div></div>

<h2 id="troubleshooting-common-issues">Troubleshooting Common Issues</h2>

<h3 id="sidecar-consuming-too-much-memory">Sidecar Consuming Too Much Memory</h3>

<p><strong>Problem:</strong> The sidecar is using more memory than expected</p>

<p><strong>Solution:</strong></p>
<ul>
  <li>Reduce <code class="language-plaintext highlighter-rouge">Mem_Buf_Limit</code> in Fluent Bit config</li>
  <li>Use more aggressive log rotation</li>
  <li>Reduce <code class="language-plaintext highlighter-rouge">Flush</code> interval</li>
  <li>Add rate limiting</li>
</ul>

<h3 id="logs-not-being-forwarded">Logs Not Being Forwarded</h3>

<p><strong>Problem:</strong> Logs are collected but not reaching the destination</p>

<p><strong>Solution:</strong></p>
<ul>
  <li>Check network connectivity: <code class="language-plaintext highlighter-rouge">kubectl exec -it pod -- curl https://log-backend:443</code></li>
  <li>Verify credentials: check API keys, authentication tokens</li>
  <li>Review Fluent Bit logs: <code class="language-plaintext highlighter-rouge">kubectl logs pod -c log-collector</code></li>
  <li>Test the output destination separately</li>
</ul>

<h3 id="high-latency-between-log-collection-and-forwarding">High Latency Between Log Collection and Forwarding</h3>

<p><strong>Problem:</strong> Logs are delayed reaching the backend</p>

<p><strong>Solution:</strong></p>
<ul>
  <li>Reduce the <code class="language-plaintext highlighter-rouge">Flush</code> interval in Fluent Bit config</li>
  <li>Increase batch size for efficiency</li>
  <li>Check network latency to the backend</li>
  <li>Monitor sidecar CPU usage</li>
</ul>

<h3 id="sidecar-crashes-when-main-app-crashes">Sidecar Crashes When Main App Crashes</h3>

<p><strong>Problem:</strong> The sidecar is terminating unexpectedly</p>

<p><strong>Solution:</strong></p>
<ul>
  <li>Use a <code class="language-plaintext highlighter-rouge">livenessProbe</code> to detect failures</li>
  <li>Ensure the sidecar has permission to read log files</li>
  <li>Check for disk space issues</li>
  <li>Increase memory limits</li>
</ul>

<h2 id="security-considerations">Security Considerations</h2>

<h3 id="sensitive-data-in-logs">Sensitive Data in Logs</h3>

<p>Be careful what gets logged:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[FILTER]</span>
    <span class="err">Name</span>    <span class="err">modify</span>
    <span class="err">Match</span>   <span class="err">*</span>
    <span class="err">Remove</span>  <span class="err">password</span>
    <span class="err">Remove</span>  <span class="err">api_key</span>
    <span class="err">Remove</span>  <span class="err">credit_card</span>
</code></pre></div></div>

<h3 id="log-file-permissions">Log File Permissions</h3>

<p>Ensure only authorized containers can read logs:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">volumeMounts</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">logs</span>
  <span class="na">mountPath</span><span class="pi">:</span> <span class="s">/var/log</span>
  <span class="na">readOnly</span><span class="pi">:</span> <span class="no">false</span>  <span class="c1"># App needs write access</span>
</code></pre></div></div>

<h3 id="encryption-in-transit">Encryption in Transit</h3>

<p>Always use TLS when sending logs to external systems:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[OUTPUT]</span>
    <span class="err">Name</span>    <span class="err">es</span>
    <span class="err">Match</span>   <span class="err">*</span>
    <span class="err">Host</span>    <span class="err">elasticsearch.example.com</span>
    <span class="err">Port</span>    <span class="err">9200</span>
    <span class="err">HTTP_User</span>  <span class="err">${ELASTIC_USER}</span>
    <span class="err">HTTP_Passwd</span> <span class="err">${ELASTIC_PASSWORD}</span>
    <span class="err">tls</span>     <span class="err">On</span>
    <span class="err">tls.verify</span>  <span class="err">Off</span>  <span class="c"># Use proper cert in production
</span></code></pre></div></div>

<h3 id="access-control">Access Control</h3>

<p>Restrict who can read logs using RBAC:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">rbac.authorization.k8s.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Role</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">log-reader</span>
<span class="na">rules</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">apiGroups</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">"</span><span class="pi">]</span>
  <span class="na">resources</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">pods/log"</span><span class="pi">]</span>
  <span class="na">verbs</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">get"</span><span class="pi">]</span>
</code></pre></div></div>

<h2 id="conclusion">Conclusion</h2>

<p>Kubernetes sidecars are a powerful pattern for solving operational concerns without modifying application code. The logging sidecar pattern specifically solves the critical problem of lost logs when containers crash.</p>

<h3 id="key-takeaways">Key Takeaways</h3>

<p><strong>The Problem:</strong> When containers crash, logs disappear, making debugging impossible</p>

<p><strong>The Solution:</strong> Run a logging sidecar in the same Pod to capture logs independently</p>

<p><strong>Why It Works:</strong></p>
<ul>
  <li>Main app crashes → Sidecar keeps running</li>
  <li>Logs are forwarded to central storage → Accessible even after restarts</li>
  <li>No app code changes needed → Works with any language or framework</li>
  <li>Resource efficient → Sidecar overhead is minimal</li>
</ul>

<p><strong>Best Practice:</strong> Use Fluent Bit by default (lightweight, Kubernetes-native, universal)</p>

<p><strong>Implementation:</strong> Takes 10-15 minutes, provides immediate value</p>

<p>The beauty of sidecars is that you don’t need to coordinate with every team, update application code, or wait for deployment cycles. You can implement this operational improvement independently and immediately see the benefits.</p>

<p>Start with logging. Once you see the power of sidecars, you’ll find more use cases: metrics, security, configuration, and more.</p>

<h2 id="further-reading">Further Reading</h2>

<ul>
  <li><a href="https://kubernetes.io/docs/concepts/workloads/pods/">Kubernetes Pod Architecture</a></li>
  <li><a href="https://docs.fluentbit.io/">Fluent Bit Documentation</a></li>
  <li><a href="https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/">Kubernetes Sidecar Containers Best Practices</a></li>
  <li><a href="https://www.cncf.io/blog/2021/05/12/towards-cloud-native-observability/">CNCF Cloud Native Observability</a></li>
</ul>]]></content><author><name>Mustaque Ahmed</name><email>amustaque97@gmail.com</email></author><category term="infra" /><category term="debugging" /><category term="kubernetes" /><category term="pipelines" /><summary type="html"><![CDATA[how we fixed our logging nightmare with Kubernetes sidecars.]]></summary></entry><entry><title type="html">Running Kubernetes Tests in CI/CD Pipelines</title><link href="https://amustaque97.github.io/run-k8s-tests-ci-cd-pipelines/" rel="alternate" type="text/html" title="Running Kubernetes Tests in CI/CD Pipelines" /><published>2025-11-01T00:00:00+05:30</published><updated>2025-11-01T00:00:00+05:30</updated><id>https://amustaque97.github.io/run-k8s-tests-ci-cd-pipelines</id><content type="html" xml:base="https://amustaque97.github.io/run-k8s-tests-ci-cd-pipelines/"><![CDATA[<p>We recently tackled an interesting networking challenge while building our GitHub Actions CI/CD pipeline for testing Kubernetes adapters. I thought I’d share the problem, the root cause, and the elegant solution we implemented.</p>

<p>The Scenario</p>

<p>Our setup involved:</p>

<ol>
  <li>
    <p>A PHP test container running on a Docker Compose orchestration network</p>
  </li>
  <li>
    <p>A KinD (Kubernetes in Docker) cluster on its own isolated kind network</p>
  </li>
  <li>
    <p>Tests that needed to communicate with the Kubernetes API server</p>
  </li>
</ol>

<p>Simple enough, right? Wrong. 😅</p>

<p><strong>The Problem</strong></p>

<p>Our K8s adapter tests were timing out when trying to reach the Kubernetes API server. The container could successfully run on the orchestration network with other services, but kubectl couldn’t authenticate with the cluster. The kubeconfig was correctly mounted, kubectl was installed, but the connection refused.</p>

<p>After digging through logs, we realized: the networks were completely isolated. Docker’s bridge networking creates firewall-like boundaries between networks. Services on the orchestration network have zero visibility into the kind network, and vice versa.</p>

<p><strong>Why This Matters</strong></p>

<p>This is actually a security feature! Docker’s network isolation prevents:</p>

<ul>
  <li>
    <p>Accidental cross-service communication</p>
  </li>
  <li>
    <p>Services interfering with each other</p>
  </li>
  <li>
    <p>Sprawling network dependencies</p>
  </li>
</ul>

<p>But in our case, we needed cross-network communication for our tests to work.</p>

<p><strong>The Solution</strong></p>

<p>Here’s where Docker’s flexibility shines. A single container can be connected to multiple networks simultaneously. This is exactly what we needed.</p>

<p>In our GitHub Actions workflow, right after starting the container with docker compose up -d, we added:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Get the test container ID</span>
<span class="sb">`</span><span class="nv">CONTAINER_ID</span><span class="o">=</span><span class="si">$(</span>docker compose ps <span class="nt">-q</span> tests<span class="si">)</span><span class="sb">`</span>

<span class="c"># Connect it to the KinD network</span>
<span class="sb">`</span>docker network connect kind <span class="s2">"</span><span class="nv">$CONTAINER_ID</span><span class="s2">"</span><span class="sb">`</span>
</code></pre></div></div>

<p>Now our test container had network access to both:</p>

<p>The orchestration network—for inter-service communication with other containers</p>

<p>The kind network—for accessing the Kubernetes API server</p>

<p>The Implementation</p>

<p>Our updated workflow step:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Start test container</span>
  <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
    <span class="s">docker compose up -d &amp;&amp; sleep 15</span>
    
    <span class="s"># Connect container to KinD network for K8s access</span>
    <span class="s">CONTAINER_ID=$(docker compose ps -q tests)</span>
    <span class="s">docker network connect kind "$CONTAINER_ID"</span>

<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Setup Docker network for KinD access</span>
  <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
    <span class="s"># Make kubeconfig accessible to containers</span>
    <span class="s">kind get kubeconfig --internal &gt; /tmp/kind-kubeconfig</span>

<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Verify connectivity</span>
  <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
    <span class="s">docker compose exec -T tests kubectl cluster-info</span>

<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Run K8s tests</span>
  <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
    <span class="s">docker compose exec -T tests vendor/bin/phpunit tests/K8sCLITest.php</span>
</code></pre></div></div>

<p><strong>Key Insights</strong></p>

<p>Docker networks aren’t monolithic—containers can be part of multiple networks</p>

<p>Isolation is the default—you have to explicitly connect networks</p>

<p>Multi-network architecture enables flexible CI/CD patterns</p>

<p>Simple shell commands in your workflow can solve complex networking challenges</p>

<p><strong>Results</strong></p>

<ul>
  <li>✅ Tests successfully connect to KinD cluster</li>
  <li>✅ No network timeouts or connection refused errors</li>
  <li>✅ Clean separation of concerns</li>
  <li>✅ Maintainable, debuggable CI/CD pipeline</li>
</ul>

<p>If you’re building CI/CD pipelines with Docker, Kubernetes, and GitHub Actions, I hope this helps! Drop a comment if you’ve encountered similar challenges.</p>]]></content><author><name>Mustaque Ahmed</name><email>amustaque97@gmail.com</email></author><category term="infra" /><category term="debugging" /><category term="docker" /><category term="pipelines" /><summary type="html"><![CDATA[Want to know at what time text got added or remoted in your entire git history?]]></summary></entry></feed>