<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.hoelzel.it/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.hoelzel.it/" rel="alternate" type="text/html" /><updated>2026-04-01T15:23:54+00:00</updated><id>https://www.hoelzel.it/feed.xml</id><title type="html">{ Hoelzel.IT }</title><subtitle>Random ramblings of an IT veteran for future documentation purposes.</subtitle><author><name>Johannes Hölzel</name></author><entry><title type="html">All roads will lead you to Azure</title><link href="https://www.hoelzel.it/compliance/2024/09/05/All-roads-lead-to-azure-eventually.html" rel="alternate" type="text/html" title="All roads will lead you to Azure" /><published>2024-09-05T00:00:00+00:00</published><updated>2024-09-05T00:00:00+00:00</updated><id>https://www.hoelzel.it/compliance/2024/09/05/All-roads-lead-to-azure-eventually</id><content type="html" xml:base="https://www.hoelzel.it/compliance/2024/09/05/All-roads-lead-to-azure-eventually.html"><![CDATA[<p>Kubernetes is the Swiss Army knife of container orchestration. It’s versatile, flexible, and can be deployed on just about any platform-from bare metal servers at Hetzner to cloud environments like DigitalOcean. But if you’re the type who rolls your own RKE2 clusters, you know that handmade infrastructure doesn’t just save you money-it gives you better control and tighter security than you’ll ever get from a typical cloud provider.</p>

<p>This flexibility is crucial, especially in a multicloud world where you’re picking the best tools for the job rather than being stuck with a one-size-fits-all approach. But here’s the rub: with all this flexibility comes complexity, especially when it comes to maintaining compliance across different environments. Kubernetes is a beast when it comes to managing containers, but it doesn’t automatically solve the compliance challenges that come with a diverse infrastructure.</p>

<h3 id="the-realities-of-a-multicloud-world">The Realities of a Multicloud World</h3>

<p>When you’re running RKE2 clusters on Hetzner and DigitalOcean, you’re not just trying to avoid vendor lock-in-you’re playing to the strengths of each platform. Hetzner gives you affordable, high-performance bare metal servers where you control every detail, from networking to security policies. DigitalOcean, on the other hand, offers quick scaling and a streamlined interface that makes it easier to manage additional resources without the overhead. This setup lets you optimize for cost, performance, and control.</p>

<p>But as anyone who’s managed a multicloud environment knows, the more platforms you bring into the mix, the more challenging it becomes to maintain a consistent security posture. Each platform has its own tools, configurations, and idiosyncrasies, which means you’re constantly juggling different security models. Without a solid strategy, this complexity can quickly spiral into a compliance nightmare.</p>

<h3 id="compliance-beyond-the-clusters">Compliance: Beyond the Clusters</h3>

<p>Securing your Kubernetes clusters is critical, but it’s only one piece of the compliance puzzle. True compliance means extending your security measures to every endpoint-every laptop, mobile device, and VM that touches your infrastructure. In a world where a single compromised endpoint can undo the security of your entire environment, you can’t afford to overlook this.</p>

<h4 id="hard-drive-encryption-the-basics-done-right">Hard Drive Encryption: The Basics, Done Right</h4>

<p>Hard drive encryption is where it all starts. If you’re dealing with sensitive data-and let’s face it, who isn’t?-you need to ensure that every device is encrypted. BitLocker, integrated with Office 365, is your go-to for Windows environments. It’s not just about ticking a box for compliance; it’s about making sure that if a device is lost or stolen, the data on it is safe. The key here is enforcing encryption across all devices, ensuring no exceptions slip through the cracks.</p>

<h4 id="advanced-threat-protection-more-than-just-antivirus">Advanced Threat Protection: More Than Just Antivirus</h4>

<p>Basic antivirus software might have cut it a decade ago, but today’s threats are far more sophisticated. You need advanced threat protection that doesn’t just rely on signature-based detection. Microsoft Defender, built into Office 365, offers real-time behavioral analysis, detecting and responding to threats as they emerge. This is about more than just stopping malware-it’s about catching anomalous behavior before it can escalate into something worse.</p>

<h4 id="automated-updates-close-the-gaps">Automated Updates: Close the Gaps</h4>

<p>Keeping every device up to date with the latest patches is crucial, especially in a distributed team where employees might be scattered across different locations. With Intune, you can automate updates across all your devices, ensuring that every endpoint is running the latest, most secure software. This isn’t just about convenience-it’s about closing vulnerabilities before they can be exploited. In a compliance-driven world, patch management isn’t optional; it’s essential.</p>

<h4 id="remote-wipe-protecting-data-when-things-go-wrong">Remote Wipe: Protecting Data When Things Go Wrong</h4>

<p>We all know that things go wrong. Devices get lost, stolen, or compromised. When that happens, you need to be able to remotely wipe those devices to protect your data. Intune’s integration with Azure AD gives you that capability. Whether it’s a laptop left in a cab or a mobile device stolen from an airport, you can ensure that your data doesn’t end up in the wrong hands. This isn’t just a nice-to-have-it’s a critical part of your compliance strategy.</p>

<h4 id="auditing-and-verification-proving-compliance-every-time">Auditing and Verification: Proving Compliance, Every Time</h4>

<p>Implementing security measures is one thing; proving they’re in place and effective is another. When it comes to compliance, you need to be able to demonstrate that your controls are working as intended. With Office 365, you get the tools to monitor and report on the security status of every device in your fleet. This means you can provide the evidence needed for audits without having to dig through endless logs or manually compile reports. In the world of compliance, being able to prove you’re compliant is just as important as actually being compliant.</p>

<h3 id="teleport-securing-kubernetes-access">Teleport: Securing Kubernetes Access</h3>

<p>Now let’s shift focus to securing access to your Kubernetes clusters. In a multicloud environment, you can’t afford to rely on old-school access methods. This is where Teleport comes in. Teleport is more than just an access proxy-it’s a security gateway designed to handle the unique challenges of a distributed, containerized environment.</p>

<h4 id="multi-factor-authentication-mfa-strengthen-your-security">Multi-Factor Authentication (MFA): Strengthen Your Security</h4>

<p>Relying on passwords alone is a recipe for disaster. Teleport integrates seamlessly with your existing identity providers to enforce MFA, adding an essential layer of security to your Kubernetes environments. MFA isn’t optional anymore-it’s the bare minimum for securing access in any modern infrastructure. By requiring multiple forms of verification, you significantly reduce the risk of unauthorized access, even if credentials are compromised.</p>

<h4 id="session-recording-keeping-a-close-eye">Session Recording: Keeping a Close Eye</h4>

<p>Teleport’s session recording feature is a game-changer for both compliance and security. It records every action taken during a session, giving you a detailed audit trail that’s invaluable for both internal reviews and compliance audits. If something goes wrong, these logs allow you to pinpoint exactly what happened and who was involved. In environments where compliance is non-negotiable, session recording isn’t just helpful-it’s essential.</p>

<h4 id="granular-access-controls-principle-of-least-privilege">Granular Access Controls: Principle of Least Privilege</h4>

<p>Granular access controls are a cornerstone of security, and Teleport excels here. By enforcing the principle of least privilege, you ensure that users only have access to the resources they need, nothing more. This minimizes the potential damage from both insider threats and external attacks. With Teleport, you can define who can access what, under what conditions, and for how long, giving you fine-grained control over your infrastructure.</p>

<h4 id="secure-global-access-no-matter-where-you-are">Secure Global Access: No Matter Where You Are</h4>

<p>In today’s world, your team is likely spread out across multiple locations, maybe even multiple continents. Secure access from anywhere isn’t just a convenience-it’s a necessity. Teleport enables secure, compliant access to your Kubernetes clusters from any location, ensuring that your team can work from anywhere without compromising on security.</p>

<h3 id="real-world-implementation-rke2-teleport-and-office-365-in-action">Real-World Implementation: RKE2, Teleport, and Office 365 in Action</h3>

<p>Let’s take a practical look at how this all comes together in a real-world scenario. Suppose you’re running RKE2 clusters on Hetzner and DigitalOcean, with Office 365 handling your endpoint security. Here’s how you’d ensure compliance across this diverse, multicloud environment.</p>

<h4 id="provisioning-with-intune">Provisioning with Intune</h4>

<p>First, every laptop is provisioned through Microsoft Intune. This ensures that the moment a device is powered on, it’s configured according to your security policies. BitLocker encryption is enabled, Microsoft Defender is up and running, and automated Windows updates are in place-all without the need for manual setup. This zero-touch provisioning approach ensures that every device starts off compliant, with no gaps or oversights.</p>

<h4 id="automated-teleport-installation">Automated Teleport Installation</h4>

<p>Using Group Policy Objects (GPOs), Teleport is automatically deployed on all devices. This ensures secure, logged access to Kubernetes clusters from day one. No one has to worry about whether they’ve got the right tools installed or if their access is secure-Teleport takes care of it all, ensuring that every connection is authenticated, authorized, and auditable.</p>

<h4 id="integration-and-immediate-productivity">Integration and Immediate Productivity</h4>

<p>With everything pre-configured, employees can start working the moment they receive their devices. They power up, connect, and within minutes they’re accessing the resources they need, securely and in compliance with company policies. This approach not only maximizes productivity but also ensures that security isn’t sacrificed for the sake of convenience.</p>

<h4 id="continuous-compliance-monitoring">Continuous Compliance Monitoring</h4>

<p>Office 365 and Intune provide continuous monitoring of all devices, ensuring ongoing compliance. Regular audits are conducted to verify encryption status, antivirus definitions, and software update levels. Teleport’s logging and session recording features also play a critical role in ensuring that access to Kubernetes clusters is always compliant with your security policies.</p>

<h3 id="audit-logging-the-foundation-of-compliance">Audit Logging: The Foundation of Compliance</h3>

<p>Audit logging isn’t just a technical requirement-it’s the foundation of any effective compliance strategy. Kubernetes provides native logging for API requests, which gives you a baseline level of visibility into user activities within your clusters. But for environments with strict compliance requirements, you need more.</p>

<h4 id="enhanced-logging-with-teleport">Enhanced Logging with Teleport</h4>

<p>Teleport elevates your logging capabilities, capturing every action taken during a session. This level of detail is essential for both security and compliance, allowing you to quickly identify and respond to issues as they arise. Whether it’s for troubleshooting, auditing, or forensic analysis, having detailed logs is crucial for maintaining control over your environment.</p>

<h4 id="siem-integration">SIEM Integration</h4>

<p>For organizations that need to centralize their monitoring and make sense of large volumes of log data, integrating Kubernetes logs with a Security Information and Event Management (SIEM) system is essential. SIEM integration allows you to detect anomalies in real-time, correlate events across your infrastructure, and streamline the process of generating compliance reports. It’s about turning raw log data into actionable insights that keep your infrastructure secure and compliant.</p>

<h3 id="managing-apple-devices-with-intune-and-apple-business-manager">Managing Apple Devices with Intune and Apple Business Manager</h3>

<p>In a world where your infrastructure is only as secure as its weakest link,</p>

<p>managing Apple devices can’t be an afterthought. If your organization uses Apple devices, Intune, combined with Apple Business Manager, provides a seamless way to manage these devices with the same rigor you apply to your other endpoints.</p>

<h4 id="seamless-integration-with-apple-business-manager">Seamless Integration with Apple Business Manager</h4>

<p>Apple Business Manager integrates with Intune to streamline the management of Apple devices. This means devices can be automatically enrolled in Intune right out of the box, ensuring they comply with your security policies from the moment they’re powered on. This is especially useful for organizations that need to manage a large fleet of devices with minimal manual intervention.</p>

<h4 id="enforcing-security-policies">Enforcing Security Policies</h4>

<p>Just like with Windows devices, Intune allows you to enforce security policies on Apple devices. This includes everything from enforcing encryption to managing software updates and applying advanced threat protection. And if a device is lost or stolen, you can remotely wipe it, ensuring that sensitive data remains protected.</p>

<h4 id="unified-compliance-reporting">Unified Compliance Reporting</h4>

<p>By managing both Windows and Apple devices through Intune, you get a unified view of your entire endpoint environment. This simplifies the process of auditing and ensures that all devices, regardless of platform, are held to the same security standards. In a multicloud, multi-device world, having a single pane of glass to manage compliance is a significant advantage.</p>

<h3 id="wrapping-it-up-securing-and-managing-compliance-in-a-multicloud-world">Wrapping It Up: Securing and Managing Compliance in a Multicloud World</h3>

<p>Running Kubernetes in a multicloud environment gives you the flexibility to optimize your infrastructure, but it also adds complexity, especially when it comes to security and compliance. By integrating tools like Teleport, Office 365, and Intune, you can effectively manage this complexity, ensuring that your infrastructure is not only functional but also secure and compliant.</p>

<p>Ensuring compliance across diverse environments requires a comprehensive approach-one that secures every endpoint, controls access to your clusters, and maintains detailed audit logs. With the right tools in place, you can build an infrastructure that’s both powerful and secure, keeping your operations running smoothly, no matter where your infrastructure or your team is located.</p>]]></content><author><name>Johannes Hölzel</name></author><category term="compliance" /><category term="teleport" /><category term="kubernetes" /><category term="intune" /><category term="opinion" /><category term="Office 365" /><summary type="html"><![CDATA[Kubernetes is the Swiss Army knife of container orchestration. It’s versatile, flexible, and can be deployed on just about any platform-from bare metal servers at Hetzner to cloud environments like DigitalOcean. But if you’re the type who rolls your own RKE2 clusters, you know that handmade infrastructure doesn’t just save you money-it gives you better control and tighter security than you’ll ever get from a typical cloud provider.]]></summary></entry><entry><title type="html">Gaining Total Control of Your Kubernetes Nodes with Custom Images</title><link href="https://www.hoelzel.it/kubernetes/2024/09/05/kubernetes-custom-images.html" rel="alternate" type="text/html" title="Gaining Total Control of Your Kubernetes Nodes with Custom Images" /><published>2024-09-05T00:00:00+00:00</published><updated>2024-09-05T00:00:00+00:00</updated><id>https://www.hoelzel.it/kubernetes/2024/09/05/kubernetes-custom-images</id><content type="html" xml:base="https://www.hoelzel.it/kubernetes/2024/09/05/kubernetes-custom-images.html"><![CDATA[<p>When managing Kubernetes clusters, ensuring that every node is secure, consistent, and optimized is crucial. We’ve all experienced situations where nodes behave unexpectedly due to configuration drift, outdated software, or poorly maintained base images. A powerful solution to these problems is using <strong>custom images</strong>-the OS-level equivalent of well-crafted container images. These images guarantee that every node you provision is identical, secure, and optimized for your workloads. In this article, we’ll dive deep into how they provide total control, enhance security, and streamline operations in Kubernetes clusters, particularly when used with RKE2.</p>

<h3 id="what-are-custom-images">What Are Custom Images?</h3>

<p>Custom images or sometimes called “golden images” are immutable, pre-configured system templates that include the operating system, necessary software, and specific configurations tailored to your environment. They serve as the foundation for every node in a Kubernetes cluster, whether it’s a control plane, worker node, or specialized component like a load balancer.</p>

<p>They are similar to container images in that they strip away unnecessary components, leaving only what’s essential for your Kubernetes environment. These images can be versioned and reused, ensuring consistency, security, and performance across nodes and environments.</p>

<h3 id="why-could-they-be-essential-for-kubernetes-deployments">Why could they be Essential for Kubernetes Deployments</h3>

<h4 id="1-consistency-across-your-cluster">1. Consistency Across Your Cluster</h4>

<p>One of the biggest challenges in Kubernetes environments is maintaining consistency across nodes, especially when scaling dynamically. Over time, configuration drift can cause nodes to behave unpredictably, leading to hard-to-diagnose issues.</p>

<p>Custom images eliminate this risk by ensuring that every node starts from an identical, pre-tested configuration. Whether you’re spinning up control plane nodes or worker nodes, each node will have the same OS version, configuration settings, and software stack. This uniformity makes troubleshooting easier and ensures that scaling operations are smooth and predictable.</p>

<p>The consistency provided by them also extends across environments, such as development, staging, and production. With the same image in each environment, you can be confident that any issues found in testing won’t reappear due to configuration differences in production.</p>

<h4 id="2-security-by-design">2. Security by Design</h4>

<p>Public cloud images often come with extra packages, services, or outdated components that introduce security vulnerabilities. Additionally, these images are updated according to the cloud provider’s schedule, which means you might not always have the latest security patches applied when needed.</p>

<p>With custom images, <strong>you control what’s included</strong>. You can:</p>
<ul>
  <li>Start with a minimal OS, stripping out unnecessary components to reduce the attack surface.</li>
  <li><strong>Apply security hardening</strong> by disabling unnecessary services, enforcing secure SSH configurations, and locking down permissions.</li>
  <li>Ensure compliance with industry standards, such as <strong>CIS benchmarks</strong>, right from the start.</li>
</ul>

<p>By maintaining and regularly updating your own images, you ensure that every node is fully patched and secure when it’s provisioned. This <strong>immutable infrastructure</strong> approach eliminates configuration drift and ensures that each node is in a known, secure state from the moment it joins your cluster.</p>

<h4 id="3-speeding-up-node-provisioning">3. Speeding Up Node Provisioning</h4>

<p>One of the biggest benefits of such images is the speed at which they allow you to provision new nodes. Traditional setups rely on cloud-init scripts or post-boot configuration steps, which can be slow and error-prone. If a script fails, you could end up with an incomplete node configuration, leading to instability.</p>

<p>With custom images, all the required configurations, binaries, and tools-such as <strong>RKE2</strong>, container runtimes, and monitoring agents-are baked into the image. This means nodes are ready to join your cluster as soon as they boot, without the need for lengthy initialization scripts. In dynamic environments where nodes are frequently scaled up or down, this <strong>rapid provisioning</strong> significantly reduces operational delays.</p>

<h4 id="4-full-control-over-your-node-environment">4. Full Control Over Your Node Environment</h4>

<p>Using cloud provider images often means you’re stuck with whatever the provider includes, such as pre-installed vendor-specific agents or unnecessary software that might not align with your Kubernetes environment. Worse, the cloud provider can update these images without notice, introducing unexpected changes to your infrastructure.</p>

<p>With custom images, <strong>you’re in control</strong>. You select the base OS, the installed software, and the kernel version. You can fine-tune system configurations and security policies to meet the specific needs of your Kubernetes workloads. For example:</p>
<ul>
  <li><strong>Optimize kernel parameters</strong> for performance in Kubernetes environments.</li>
  <li><strong>Remove unnecessary services</strong> and packages to minimize the attack surface and improve node efficiency.</li>
  <li><strong>Customize security policies</strong> to enforce organization-wide standards.</li>
</ul>

<p>They also allow for <strong>vendor independence</strong>, ensuring that your nodes are configured identically across multiple cloud providers or on-premise environments. This flexibility is particularly important for organizations using a <strong>multi-cloud</strong> or <strong>hybrid-cloud</strong> strategy.</p>

<h4 id="5-embedding-custom-scripts-and-self-written-programs">5. Embedding Custom Scripts and Self-Written Programs</h4>

<p>One of the biggest advantages of their is the ability to <strong>embed your own tools, scripts, and self-written programs</strong> directly into the image, far exceeding what public cloud images can offer.</p>

<p>For example:</p>
<ul>
  <li><strong>Custom Go programs</strong> can be pre-installed to automate node-specific tasks, like gathering enhanced metrics for autoscaling decisions or enforcing workload-specific policies.</li>
  <li><strong>Automation scripts</strong> can be embedded to handle logging, monitoring, and security scanning without needing additional setup post-deployment.</li>
  <li>You can pre-install <strong>custom agents</strong> to monitor network activity or handle distributed logging, ensuring every node is configured consistently.</li>
</ul>

<p>This flexibility allows you to create nodes that are fully prepared to handle the exact tasks and configurations required, eliminating the need for additional manual setup and providing a level of customization that public cloud images cannot match.</p>

<h3 id="real-world-example-financial-app-deployment-on-hetzner-and-digitalocean">Real-World Example: Financial App Deployment on Hetzner and DigitalOcean</h3>

<p>In a real-world scenario, I worked with a financial application where custom images were implemented across infrastructure on Hetzner and DigitalOcean. The primary goal was to ensure fast, consistent node provisioning while maintaining strict security standards.</p>

<p>Here’s how they were implemented:</p>
<ul>
  <li><strong>Compliance</strong>: Every image was built to comply with <strong>CIS benchmarks</strong>, starting from a hardened base OS. This ensured that each node was secure from the start, with unnecessary services disabled and access tightly controlled.</li>
  <li><strong>Role-Specific Optimization</strong>: Different images were created for control plane nodes, worker nodes, agents, and load balancers. Each image was optimized for its specific task, ensuring that nodes were tailored to their role in the cluster.</li>
  <li><strong>Go-Based Access Proxy</strong>: The <strong>Teleport Access Proxy</strong> (written in Go) was baked into the images to provide secure, auditable access to each node. This ensured compliance with strict security and access control policies, with minimal post-deployment configuration.</li>
  <li><strong>Fast Provisioning</strong>: Node provisioning times were reduced to minutes, even in a highly regulated environment. With everything pre-configured in the image, nodes could immediately join the cluster without needing time-consuming cloud-init scripts or additional configuration steps.</li>
</ul>

<p>The remaining infrastructure-such as networking, firewalls, and VPCs-was managed through <strong>Terraform</strong>, allowing for a highly automated and consistent deployment process. This combination of custom images and Terraform greatly simplified the process of deploying compliant, secure, and scalable infrastructure.</p>

<h3 id="avoiding-cloud-provider-limitations">Avoiding Cloud Provider Limitations</h3>

<p>Cloud provider images often come with <strong>vendor-specific tools</strong>, logging agents, and configurations that might not be relevant to your Kubernetes environment. Additionally, they are updated on the provider’s timeline, which can leave you with outdated packages or unanticipated changes.</p>

<p>You <strong>eliminate these limitations</strong>:</p>
<ul>
  <li>You can build the image exactly as needed, without the bloat of vendor-specific agents or unnecessary services.</li>
  <li>You control when updates are applied, ensuring that your infrastructure remains secure and stable on your terms, not the provider’s.</li>
  <li>In <strong>multi-cloud environments</strong>, using custom images allows for consistent node configurations across different cloud platforms, simplifying operations and avoiding vendor lock-in.</li>
</ul>

<h3 id="automating-image-updates-and-infrastructure-with-terraform">Automating Image Updates and Infrastructure with Terraform</h3>

<p>Maintaining and updating images requires a streamlined process to ensure that they remain secure and up to date with the latest patches and software releases. Integrating your image-building pipeline with <strong>CI/CD automation</strong> and <strong>Terraform</strong> helps ensure that your infrastructure is always in sync with the latest configurations.</p>

<h4 id="1-cicd-integration-for-automated-image-building">1. CI/CD Integration for Automated Image Building</h4>
<p>By integrating custom image builds into your CI/CD pipeline, you can automatically trigger new builds whenever there are security patches or software updates. A typical workflow looks like this:</p>
<ul>
  <li><strong>Trigger a Build</strong>: The CI/CD pipeline triggers an image build whenever a patch is released or a configuration change is required.</li>
  <li><strong>Automated Testing</strong>: Once the image is built, automated tests are run to ensure it works as expected. This includes security scans, performance tests, and conformance checks.</li>
  <li><strong>Versioning and Rollout</strong>: Each image is versioned, ensuring that changes are tracked and that you can easily roll back to a previous version if needed. Once tested, the new image is rolled out incrementally to ensure stability.</li>
</ul>

<h4 id="2-terraform-for-infrastructure-provisioning">2. Terraform for Infrastructure Provisioning</h4>
<p>While custom images take care of node configuration, <strong>Terraform</strong> manages the surrounding infrastructure, such as networking, firewalls, and VPCs. Terraform automates the provisioning of the entire Kubernetes environment, ensuring that:</p>
<ul>
  <li>Nodes are deployed with the correct network configurations.</li>
  <li><strong>Firewall rules</strong> are enforced, ensuring that only necessary ports are open.</li>
  <li>The infrastructure is versioned and tracked, making it easy to replicate environments or roll back changes.</li>
</ul>

<p>Using Terraform allows for a fully automated, <strong>infrastructure-as-code</strong> approach to managing Kubernetes deployments, from node configuration to networking and security.</p>

<h3 id="edge-computing-and-custom-images-expanding-kubernetes-beyond-the-cloud">Edge Computing and Custom Images: Expanding Kubernetes Beyond the Cloud</h3>

<p>As Kubernetes expands beyond traditional cloud environments into <strong>edge computing</strong>, custom images become even more valuable. Edge nodes are often resource-constrained and operate in environments with limited network connectivity. They can be optimized to handle these unique requirements by:</p>
<ul>
  <li>Stripping down the OS and minimizing the resource footprint to improve performance on constrained hardware.</li>
  <li>
    <p>Preloading essential components and enabling local caching to reduce reliance on network connectivity.</p>
  </li>
  <li>Ensuring that nodes can function autonomously when disconnected from the control plane.</li>
</ul>

<p>Specialized images designed for edge environments allow Kubernetes to be deployed in remote locations with minimal infrastructure while still ensuring consistency and security.</p>

<h3 id="long-term-strategy-evolving-your-infrastructure">Long-Term Strategy: Evolving Your Infrastructure</h3>

<p>Custom images are not just about solving today’s problems-they’re about future-proofing your Kubernetes infrastructure. As workloads evolve, compliance standards become stricter, and new technologies like <strong>AI/ML</strong> or <strong>edge computing</strong> take hold, they provide a flexible foundation that can adapt to new requirements without significant reengineering.</p>

<h4 id="1-modular-and-adaptable">1. Modular and Adaptable</h4>
<p>Images can be designed to be modular, allowing you to build images optimized for specific workloads. For example, you might have:</p>
<ul>
  <li>A lightweight image for resource-constrained environments (e.g., edge nodes).</li>
  <li>A high-performance image optimized for AI/ML workloads with pre-installed libraries like TensorFlow or PyTorch.</li>
</ul>

<h4 id="2-collaboration-between-devops-and-development-teams">2. Collaboration Between DevOps and Development Teams</h4>
<p>Custom images help ensure that development, staging, and production environments are consistent. By embedding standard tools, libraries, and runtime environments directly into the images, you reduce the likelihood of “works on my machine” or “works in staging” issues. This accelerates collaboration between DevOps and development teams, enabling faster debugging and fewer production issues.</p>

<h3 id="3-they-life-in-git">3. They life in GIT</h3>
<p>These images can easiy withstand auditing procedures by using proper GitOps practises. Environments are not only clearly built with a version history but with the use of github actions, building your image becomes an easy workflow like this:</p>

<pre><code class="language-YAML">name: Build All DigitalOcean Snapshots

on:
  workflow_dispatch:

jobs:
  build_digitalocean:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Set up Packer
        uses: hashicorp/setup-packer@v1

      - name: Build DigitalOcean snapshots
        run: make build-digitalocean
        env:
          DO_TOKEN: $
          DISCORD_WEBHOOK_URL: $

</code></pre>
<h2 id="deep-dive-automating-custom-kubernetes-images-with-packer">Deep Dive: Automating Custom Kubernetes Images with Packer</h2>

<p>When managing Kubernetes clusters in any environment, using custom images ensures consistency, security, and speed during node provisioning. <strong>HashiCorp Packer</strong> simplifies the creation of these images, automating their build process so they can be pre-configured, secure, and Kubernetes-ready.</p>

<p>In this chapter, we’ll walk through using Packer to build a custom <strong>K3s</strong> image for <strong>DigitalOcean</strong>, ensuring the image is optimized, hardened, and lean. This approach minimizes the need for manual node configuration, making your infrastructure more efficient and reliable.
This is of course not an complete example, yet I think it will get the point across.</p>

<h4 id="packer-template-for-digitalocean">Packer Template for DigitalOcean</h4>

<p>This template provisions a <strong>K3s-ready</strong> image on DigitalOcean using Ubuntu, with firewall settings, SSH hardening, and clean-up steps to ensure the image is secure and lightweight. Importantly, it includes a reset of <strong>cloud-init</strong> to ensure fresh configuration on new instances and of course if you like, even initialize the image itself with a cloud-config.</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">source</span> <span class="s2">"digitalocean"</span> <span class="s2">"ubuntu-k3s"</span> <span class="p">{</span>
  <span class="nx">image</span>       <span class="p">=</span> <span class="s2">"ubuntu-20-04-x64"</span>
  <span class="nx">region</span>      <span class="p">=</span> <span class="s2">"nyc3"</span>
  <span class="nx">size</span>        <span class="p">=</span> <span class="nx">var</span><span class="err">.</span><span class="nx">instance_size</span>
  <span class="nx">ssh_username</span> <span class="p">=</span> <span class="s2">"root"</span>
<span class="p">}</span>

<span class="nx">build</span> <span class="p">{</span>
  <span class="nx">name</span>    <span class="p">=</span> <span class="s2">"ubuntu-k3s-build"</span>
  <span class="nx">sources</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"source.digitalocean.ubuntu-k3s"</span><span class="p">]</span>

  <span class="nx">provisioner</span> <span class="s2">"shell"</span> <span class="p">{</span>
    <span class="nx">inline</span> <span class="p">=</span> <span class="p">[</span>
      <span class="c1"># Update the system and install necessary tools</span>
      <span class="s2">"apt-get update -y"</span><span class="p">,</span>
      <span class="s2">"apt-get upgrade -y"</span><span class="p">,</span>
      <span class="s2">"apt-get install -y curl ufw"</span><span class="p">,</span>

      <span class="c1"># Set up firewall rules for security</span>
      <span class="s2">"ufw allow OpenSSH"</span><span class="p">,</span>       <span class="c1"># SSH access</span>
      <span class="s2">"ufw allow 6443/tcp"</span><span class="p">,</span>      <span class="c1"># K3s API port</span>
      <span class="s2">"ufw allow 8472/udp"</span><span class="p">,</span>      <span class="c1"># Flannel networking for K3s</span>
      <span class="s2">"ufw allow 10250/tcp"</span><span class="p">,</span>     <span class="c1"># Kubelet communication</span>
      <span class="s2">"ufw enable"</span><span class="p">,</span>              <span class="c1"># Enable firewall</span>

      <span class="c1"># Disable password-based SSH for added security</span>
      <span class="s2">"sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config"</span><span class="p">,</span>
      <span class="s2">"systemctl restart sshd"</span><span class="p">,</span>

      <span class="c1"># Install K3s (lightweight Kubernetes)</span>
      <span class="s2">"curl -sfL https://get.k3s.io | sh -"</span><span class="p">,</span>

    <span class="p">]</span>
  <span class="p">}</span>

  <span class="c1"># Clean up to minimize image size</span>
  <span class="nx">provisioner</span> <span class="s2">"shell"</span> <span class="p">{</span>
    <span class="nx">inline</span> <span class="p">=</span> <span class="p">[</span>
      <span class="s2">"apt-get clean"</span><span class="p">,</span>                       <span class="c1"># Clean apt cache</span>
      <span class="s2">"rm -rf /var/lib/apt/lists/*"</span><span class="p">,</span>         <span class="c1"># Remove apt lists</span>
      <span class="s2">"rm -rf /tmp/*"</span><span class="p">,</span>                       <span class="c1"># Clean /tmp</span>
      <span class="s2">"rm -rf /var/tmp/*"</span>                    <span class="c1"># Clean /var/tmp</span>
    <span class="p">]</span>
  <span class="p">}</span>

  <span class="c1"># Reset cloud-init so the image is fresh for future instances</span>
  <span class="nx">provisioner</span> <span class="s2">"shell"</span> <span class="p">{</span>
    <span class="nx">inline</span> <span class="p">=</span> <span class="p">[</span>
      <span class="c1"># Stop cloud-init to reset its state</span>
      <span class="s2">"systemctl stop cloud-init"</span><span class="p">,</span>

      <span class="c1"># Clean up cloud-init logs and state to ensure new instance gets fresh initialization</span>
      <span class="s2">"rm -rf /var/lib/cloud/"</span><span class="p">,</span>
      <span class="s2">"rm -rf /var/log/cloud-init.log /var/log/cloud-init-output.log"</span><span class="p">,</span>

      <span class="c1"># Ensure cloud-init runs on new instances</span>
      <span class="s2">"touch /etc/cloud/cloud-init.disabled"</span><span class="p">,</span>   <span class="c1"># Temporarily disable cloud-init</span>
      <span class="s2">"rm /etc/cloud/cloud-init.disabled"</span>       <span class="c1"># Re-enable for the next boot</span>
    <span class="p">]</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="lets-dive-into-the-details">Let’s dive into the details</h3>

<ol>
  <li><strong>Source Configuration</strong>:
    <ul>
      <li>The source uses <strong>Ubuntu 20.04</strong> on DigitalOcean, with flexibility to select the instance size via the <code class="language-plaintext highlighter-rouge">var.instance_size</code> variable.</li>
      <li>The image is built in the <strong>nyc3</strong> region, and <code class="language-plaintext highlighter-rouge">root</code> is used as the SSH user during the image build process.</li>
    </ul>
  </li>
  <li><strong>Provisioning</strong>:
    <ul>
      <li><strong>System Updates and Tools</strong>: Updates the OS and installs essential tools like <strong>curl</strong> and <strong>UFW</strong> (firewall).</li>
      <li><strong>Firewall Setup</strong>: Configures UFW to allow only required ports:
        <ul>
          <li><strong>SSH (OpenSSH)</strong>: For secure SSH access.</li>
          <li><strong>K3s API (6443/tcp)</strong>: To allow K3s communication.</li>
          <li><strong>Flannel (8472/udp)</strong>: For Kubernetes networking.</li>
          <li><strong>Kubelet (10250/tcp)</strong>: Ensures Kubelet communication.</li>
        </ul>
      </li>
      <li><strong>SSH Hardening</strong>: Disables password-based SSH access to enforce key-based authentication, adding another layer of security.</li>
      <li><strong>K3s Installation</strong>: Downloads and installs <strong>K3s</strong>, the lightweight Kubernetes distribution, making the node ready for your cluster.</li>
    </ul>
  </li>
  <li><strong>Image Cleanup</strong>:
    <ul>
      <li>After provisioning, the template runs a cleanup process to remove unnecessary files, package lists, and temporary data. This step is essential for keeping the image lightweight and secure by reducing the potential attack surface and improving node performance.</li>
    </ul>
  </li>
  <li><strong>Resetting Cloud-Init</strong>:
    <ul>
      <li><strong>Cloud-init</strong> is reset to ensure that when a new instance is created from this image, it runs a fresh cloud-init cycle, pulling the correct instance-specific data like networking and metadata configurations.</li>
      <li>Logs and cloud-init states are wiped clean, guaranteeing that each new instance starts without residual configuration from the original build process.</li>
    </ul>
  </li>
</ol>

<h3 id="why-this-template-works-for-kubernetes">Why This Template Works for Kubernetes</h3>

<p>This Packer template is designed to make <strong>DigitalOcean</strong> Kubernetes nodes more efficient and secure. It ensures that each node is:</p>
<ul>
  <li><strong>Pre-configured and consistent</strong>: With all necessary software and security settings baked into the image, every node will behave the same way, reducing potential configuration drift.</li>
  <li><strong>Hardened for security</strong>: The template applies some security best practices, including firewall rules and SSH hardening, minimizing potential attack vectors. You can see how we can easily go even much deeper if we wanted to.</li>
  <li><strong>Optimized for Kubernetes</strong>: K3s is pre-installed, and the node is verified as ready to join the cluster, reducing the need for post-boot configurations.</li>
</ul>

<p>By automating the creation of custom Kubernetes images using Packer, you gain control over how your nodes are built, ensuring they are <strong>secure</strong>, <strong>consistent</strong>, and <strong>ready to scale</strong>. This template demonstrates how to efficiently build K3s-ready nodes that are hardened, optimized, and require minimal manual setup. By integrating Packer into your Kubernetes workflow, you can basically do what you want and also save time, increase operational security, and improve the overall efficiency of your infrastructure.</p>

<h4 id="what-now">What now?</h4>

<p>With this setup you can initialize the kubernetes node by simply providing the k3s config through cloud-init in terraform. Or through the provider API. Or even through kubernetes operators ;).</p>

<h3 id="tldr-custom-images-as-the-backbone-of-kubernetes-success">TLDR: Custom Images as the Backbone of Kubernetes Success</h3>

<p>In today’s fast-paced cloud-native environments, where scalability, security, and flexibility are critical, <strong>custom images</strong> are the backbone of successful Kubernetes deployments. By building and maintaining them tailored to your workloads, you ensure that your nodes are consistently configured, secure, and ready to meet the demands of production environments-whether in the cloud, at the edge, or on-prem.</p>

<p>From <strong>fast provisioning</strong> and <strong>automated updates</strong> to <strong>resource optimization</strong> and <strong>enhanced resilience</strong>, they are a powerful tool for any organization leveraging Kubernetes. When combined with automation tools like <strong>Terraform</strong> and integrated into a broader <strong>DevOps</strong> strategy and offer the control and flexibility needed to manage modern infrastructure at scale.</p>

<p>By adopting golden images, you are not only solving the challenges of today but also preparing your infrastructure for the future-whether that involves scaling across multiple clouds, expanding to edge environments, or adopting new workloads. They give you the foundation to build a Kubernetes environment that is predictable, secure, and adaptable to the ever-changing demands of modern cloud-native applications.</p>]]></content><author><name>Johannes Hölzel</name></author><category term="kubernetes" /><category term="ci" /><category term="kubernetes-orchestration" /><summary type="html"><![CDATA[When managing Kubernetes clusters, ensuring that every node is secure, consistent, and optimized is crucial. We’ve all experienced situations where nodes behave unexpectedly due to configuration drift, outdated software, or poorly maintained base images. A powerful solution to these problems is using custom images-the OS-level equivalent of well-crafted container images. These images guarantee that every node you provision is identical, secure, and optimized for your workloads. In this article, we’ll dive deep into how they provide total control, enhance security, and streamline operations in Kubernetes clusters, particularly when used with RKE2.]]></summary></entry><entry><title type="html">Building Resilience with kube-probesim</title><link href="https://www.hoelzel.it/kubernetes/2024/09/02/kube-probesim.html" rel="alternate" type="text/html" title="Building Resilience with kube-probesim" /><published>2024-09-02T00:00:00+00:00</published><updated>2024-09-02T00:00:00+00:00</updated><id>https://www.hoelzel.it/kubernetes/2024/09/02/kube-probesim</id><content type="html" xml:base="https://www.hoelzel.it/kubernetes/2024/09/02/kube-probesim.html"><![CDATA[<p>As the creator of <code class="language-plaintext highlighter-rouge">kube-probesim</code>, I wanted to solve a specific problem: simulating how Kubernetes applications handle liveness and readiness probe failures. This tool was born out of a need to quickly replicate real-world failure conditions like random probe failures, latency spikes, and failing external dependencies in a controlled manner.</p>

<p>For many Kubernetes developers and operators, building resilient systems requires more than just passing happy path tests. You need to understand how your application behaves under stress or failure. Kubernetes’ liveness and readiness probes are crucial in this respect, but testing them without good tools can be tricky. That’s where <code class="language-plaintext highlighter-rouge">kube-probesim</code> comes in. Let me explain how you can leverage it and how easy it is to deploy, thanks to its availability in the GitHub Container Registry.</p>

<hr />

<h4 id="why-kube-probesim">Why kube-probesim?</h4>

<p>When I first started using Kubernetes in production, one of the challenges was ensuring applications were resilient enough to withstand failures-especially during scale-ups, unexpected traffic spikes, or external service downtimes. Probes (liveness and readiness) play a crucial role in ensuring the stability of the system, but testing these probes in realistic scenarios is difficult. <code class="language-plaintext highlighter-rouge">kube-probesim</code> simplifies this.</p>

<p>Here’s what it does:</p>

<ul>
  <li><strong>Probe Failures</strong>: Simulate random or time-triggered liveness/readiness probe failures.</li>
  <li><strong>Latency Simulation</strong>: Introduce configurable network latencies.</li>
  <li><strong>External Dependency Failure</strong>: Simulate downstream service failures.</li>
  <li><strong>Network Partitioning</strong>: Test behavior under simulated network issues.</li>
</ul>

<p>The goal is to empower developers and operators to test probe handling under controlled, configurable failure conditions without introducing a lot of complexity.</p>

<hr />

<h4 id="deploying-kube-probesim-from-github-container-registry">Deploying kube-probesim from GitHub Container Registry</h4>

<p>Since I’m hosting <code class="language-plaintext highlighter-rouge">kube-probesim</code> in the GitHub Container Registry, deploying it into any Kubernetes cluster is a breeze. No need to build the image yourself-just pull the container directly from the registry.</p>

<p>Here’s how you can do it in your own environment:</p>

<ol>
  <li>
    <p><strong>Add kube-probesim Deployment</strong></p>

    <p>Create a Kubernetes deployment YAML with the GitHub-hosted container image:</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">kube-probesim</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">replicas</span><span class="pi">:</span> <span class="m">1</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">kube-probesim</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">kube-probesim</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">kube-probesim</span>
        <span class="na">image</span><span class="pi">:</span> <span class="s">ghcr.io/jhoelzel/kube-probesim:latest</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">env</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">FAILURE_RATE</span>
          <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">20"</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">LATENCY</span>
          <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">50"</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">LIVENESS_FAIL_AFTER_TIME</span>
          <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">60"</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">READINESS_FAIL_AFTER_TIME</span>
          <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">120"</span>
</code></pre></div>    </div>

    <p>This YAML uses the official <code class="language-plaintext highlighter-rouge">kube-probesim</code> container from GitHub’s Container Registry, configuring it with a 20% failure rate and 50ms of added latency.</p>
  </li>
  <li>
    <p><strong>Deploy it</strong></p>

    <p>Apply the YAML to your Kubernetes cluster:</p>

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

    <p>Kubernetes will pull the image directly from GitHub and start the <code class="language-plaintext highlighter-rouge">kube-probesim</code> pod with the parameters you’ve set.</p>
  </li>
</ol>

<hr />

<h4 id="real-world-scenarios">Real-World Scenarios</h4>

<p>Here’s how I’ve used <code class="language-plaintext highlighter-rouge">kube-probesim</code> in the wild:</p>

<h5 id="1-random-failures">1. <strong>Random Failures</strong></h5>
<p>To simulate a 10% failure rate across both probes, with a 100ms response delay:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>   <span class="nv">FAILURE_RATE</span><span class="o">=</span>10 <span class="nv">LATENCY</span><span class="o">=</span>100 ./kube-probesim
</code></pre></div></div>

<p>This allows me to test how Kubernetes reschedules pods when failures start happening unpredictably, helping ensure the system can handle such events without downtime.</p>

<h5 id="2-failing-external-dependencies">2. <strong>Failing External Dependencies</strong></h5>
<p>Using the <code class="language-plaintext highlighter-rouge">/dependency</code> endpoint with <code class="language-plaintext highlighter-rouge">FAIL_DEPENDENCY=true</code> helped me test scenarios where downstream services were unavailable, helping me tweak timeouts and retries for critical service dependencies.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>   <span class="nv">FAIL_DEPENDENCY</span><span class="o">=</span><span class="nb">true</span> ./kube-probesim
</code></pre></div></div>

<hr />

<h4 id="observability-and-monitoring">Observability and Monitoring</h4>

<p>If you’re running <code class="language-plaintext highlighter-rouge">kube-probesim</code> in a production-like environment, don’t forget to integrate your monitoring and logging stack (e.g., Prometheus or Grafana). Observing probe failures over time will help you understand how Kubernetes reacts and will give you insights into system recovery times and automatic pod rescheduling.</p>

<h4 id="tldr-a-tool-to-simulate-probe-failure">TLDR: A tool to simulate probe failure</h4>

<p>The beauty of <code class="language-plaintext highlighter-rouge">kube-probesim</code> lies in its flexibility and how easy it is to deploy thanks to its GitHub Container Registry image. Whether you need to test random failures, network issues, or external service dependencies, <code class="language-plaintext highlighter-rouge">kube-probesim</code> lets you simulate these failures in a safe, controlled environment. It’s a simple but powerful way to make your applications more resilient before they go live in production.</p>

<h3 id="next-steps">Next Steps:</h3>
<ul>
  <li><strong>Get the image from the <a href="https://github.com/jhoelzel/kube-probesim/pkgs/container/kube-probesim">GitHub Container Registry</a>.</strong></li>
  <li><strong>Deploy <code class="language-plaintext highlighter-rouge">kube-probesim</code> in your staging environment, and start testing!</strong></li>
</ul>

<p>Remember, production is unforgiving. <code class="language-plaintext highlighter-rouge">kube-probesim</code> gives you the edge by helping you identify and fix failure points <em>before</em> they cause issues for real users.</p>]]></content><author><name>Johannes Hölzel</name></author><category term="kubernetes" /><category term="kubernetes-problems" /><category term="cli" /><summary type="html"><![CDATA[As the creator of kube-probesim, I wanted to solve a specific problem: simulating how Kubernetes applications handle liveness and readiness probe failures. This tool was born out of a need to quickly replicate real-world failure conditions like random probe failures, latency spikes, and failing external dependencies in a controlled manner.]]></summary></entry><entry><title type="html">go_wait_for_k8s</title><link href="https://www.hoelzel.it/kubernetes/2024/09/01/go-wait-for-k8s.html" rel="alternate" type="text/html" title="go_wait_for_k8s" /><published>2024-09-01T00:00:00+00:00</published><updated>2024-09-01T00:00:00+00:00</updated><id>https://www.hoelzel.it/kubernetes/2024/09/01/go-wait-for-k8s</id><content type="html" xml:base="https://www.hoelzel.it/kubernetes/2024/09/01/go-wait-for-k8s.html"><![CDATA[<p>In Kubernetes, ensuring that dependent services are ready before your application starts can be a critical task. For instance, your application might rely on a PostgreSQL database, and you need to make sure the database is fully initialized and ready to accept connections before the app itself starts. Handling this properly often requires custom scripts or tools to manage readiness checks, which can get complex and error-prone.</p>

<p>This is where <code class="language-plaintext highlighter-rouge">go_wait_for_k8s</code> comes in. Designed to run as an <code class="language-plaintext highlighter-rouge">InitContainer</code> within your Kubernetes pods, it ensures that critical dependencies are ready before your main application container starts. This approach guarantees that your application only launches when its dependencies are fully operational, making your deployments more robust and reliable.</p>

<h3 id="the-problem-ensuring-dependencies-are-ready">The Problem: Ensuring Dependencies are Ready</h3>

<p>When deploying applications that depend on other services-like a web application that requires a database or a cache-it’s crucial to ensure those services are ready before your application attempts to interact with them. If not handled properly, your application might fail to start or exhibit erratic behavior due to unavailable resources.</p>

<p>For example:</p>

<ul>
  <li><strong>Database Dependencies</strong>: Your application might fail to start or encounter connection issues if the database is not ready.</li>
  <li><strong>Service Dependencies</strong>: Microservices might require other services to be available and ready before they can function correctly.</li>
</ul>

<p>Traditionally, this has been managed with custom scripts or by building readiness checks directly into the application. However, these methods can be cumbersome and are often prone to failure.</p>

<h3 id="how-go_wait_for_k8s-solves-this-problem">How <code class="language-plaintext highlighter-rouge">go_wait_for_k8s</code> Solves This Problem</h3>

<p><code class="language-plaintext highlighter-rouge">go_wait_for_k8s</code> is specifically designed to run as an <code class="language-plaintext highlighter-rouge">InitContainer</code> in your Kubernetes pod, ensuring that the necessary dependencies are ready before your main application container starts. It interacts with the Kubernetes API to check the readiness of specific resources like pods, deployments, or services.</p>

<h4 id="why-use-an-initcontainer">Why Use an InitContainer?</h4>

<p>Using an <code class="language-plaintext highlighter-rouge">InitContainer</code> ensures that <code class="language-plaintext highlighter-rouge">go_wait_for_k8s</code> runs before any other containers in the pod. The <code class="language-plaintext highlighter-rouge">InitContainer</code> blocks the startup of the main application container until the specified conditions are met, guaranteeing that your application only starts when its dependencies are available.</p>

<h3 id="example-use-case-waiting-for-a-postgresql-database">Example Use Case: Waiting for a PostgreSQL Database</h3>

<p>Let’s consider a common scenario where your application relies on a PostgreSQL database. You want to ensure that the PostgreSQL deployment is fully rolled out and the pod is ready before your application starts. Here’s how you can use <code class="language-plaintext highlighter-rouge">go_wait_for_k8s</code> to handle this scenario.</p>

<h4 id="step-1-define-the-initcontainer-in-your-pod-spec">Step 1: Define the <code class="language-plaintext highlighter-rouge">InitContainer</code> in Your Pod Spec</h4>

<p>In your application’s Kubernetes deployment, you would define an <code class="language-plaintext highlighter-rouge">InitContainer</code> that runs <code class="language-plaintext highlighter-rouge">go_wait_for_k8s</code> to wait for the PostgreSQL deployment to be ready.</p>

<p>Here’s an example deployment YAML:</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">my-app</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">app</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">replicas</span><span class="pi">:</span> <span class="m">1</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">my-app</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">my-app</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">my-app</span>
        <span class="na">image</span><span class="pi">:</span> <span class="s">my-app-image:latest</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">initContainers</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">wait-for-postgres</span>
        <span class="na">image</span><span class="pi">:</span> <span class="s">ghcr.io/jhoelzel/go_wait_for_k8s:latest</span>
        <span class="na">args</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s2">"</span><span class="s">--namespace=app"</span>
        <span class="pi">-</span> <span class="s2">"</span><span class="s">--resource=deployment"</span>
        <span class="pi">-</span> <span class="s2">"</span><span class="s">--name=postgres"</span>
        <span class="pi">-</span> <span class="s2">"</span><span class="s">--condition=available"</span>
</code></pre></div></div>

<p>In this YAML:</p>

<ul>
  <li>The <code class="language-plaintext highlighter-rouge">InitContainer</code> named <code class="language-plaintext highlighter-rouge">wait-for-postgres</code> uses the <code class="language-plaintext highlighter-rouge">go_wait_for_k8s</code> image.</li>
  <li>It waits for the PostgreSQL deployment in the <code class="language-plaintext highlighter-rouge">app</code> namespace to reach the <code class="language-plaintext highlighter-rouge">available</code> condition, meaning all pods in the deployment are ready.</li>
  <li>Only after this check passes will the main application container (<code class="language-plaintext highlighter-rouge">my-app</code>) start.</li>
</ul>

<h4 id="step-2-deploy-to-kubernetes">Step 2: Deploy to Kubernetes</h4>

<p>You can deploy this configuration to your Kubernetes cluster using <code class="language-plaintext highlighter-rouge">kubectl</code>:</p>

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

<p>With this setup, the <code class="language-plaintext highlighter-rouge">InitContainer</code> ensures that your application does not start until the PostgreSQL deployment is fully ready, preventing issues related to premature starts.</p>

<h3 id="using-go_wait_for_k8s-for-other-dependencies">Using <code class="language-plaintext highlighter-rouge">go_wait_for_k8s</code> for Other Dependencies</h3>

<p>While the PostgreSQL example is common, <code class="language-plaintext highlighter-rouge">go_wait_for_k8s</code> can be used to wait for various types of resources. Here are a few examples:</p>

<ul>
  <li>
    <p><strong>Waiting for a Redis Pod</strong>: Ensure that a Redis pod is ready before starting your caching service.</p>

    <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">initContainers</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">wait-for-redis</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">ghcr.io/jhoelzel/go_wait_for_k8s:latest</span>
  <span class="na">args</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s2">"</span><span class="s">--namespace=cache"</span>
  <span class="pi">-</span> <span class="s2">"</span><span class="s">--resource=pod"</span>
  <span class="pi">-</span> <span class="s2">"</span><span class="s">--name=redis-pod"</span>
  <span class="pi">-</span> <span class="s2">"</span><span class="s">--condition=ready"</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p><strong>Waiting for an API Service</strong>: Ensure that a critical API service is available before your microservice starts.</p>

    <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">initContainers</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">wait-for-api</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">ghcr.io/jhoelzel/go_wait_for_k8s:latest</span>
  <span class="na">args</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s2">"</span><span class="s">--namespace=services"</span>
  <span class="pi">-</span> <span class="s2">"</span><span class="s">--resource=deployment"</span>
  <span class="pi">-</span> <span class="s2">"</span><span class="s">--name=api-service"</span>
  <span class="pi">-</span> <span class="s2">"</span><span class="s">--condition=available"</span>
</code></pre></div>    </div>
  </li>
</ul>

<h3 id="security-and-rbac-considerations">Security and RBAC Considerations</h3>

<p>Running <code class="language-plaintext highlighter-rouge">go_wait_for_k8s</code> as an <code class="language-plaintext highlighter-rouge">InitContainer</code> also integrates well with Kubernetes’ RBAC (Role-Based Access Control) system. By assigning a service account with minimal permissions to the <code class="language-plaintext highlighter-rouge">InitContainer</code>, you can ensure that it only has access to the resources it needs to query.</p>

<p>Here’s an example of how you might configure RBAC for <code class="language-plaintext highlighter-rouge">go_wait_for_k8s</code>:</p>

<h4 id="create-a-role-with-minimal-permissions">Create a Role with Minimal Permissions</h4>

<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">namespace</span><span class="pi">:</span> <span class="s">app</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">go-wait-for-k8s-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">deployments"</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>
</code></pre></div></div>

<h4 id="bind-the-role-to-a-service-account">Bind the Role to a Service Account</h4>

<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">RoleBinding</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">go-wait-for-k8s-binding</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">app</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">go-wait-for-k8s-sa</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">app</span>
<span class="na">roleRef</span><span class="pi">:</span>
  <span class="na">kind</span><span class="pi">:</span> <span class="s">Role</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">go-wait-for-k8s-role</span>
  <span class="na">apiGroup</span><span class="pi">:</span> <span class="s">rbac.authorization.k8s.io</span>
</code></pre></div></div>

<p>Assign this service account to the <code class="language-plaintext highlighter-rouge">InitContainer</code>:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">spec</span><span class="pi">:</span>
  <span class="na">initContainers</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">wait-for-postgres</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">ghcr.io/jhoelzel/go_wait_for_k8s:latest</span>
    <span class="na">args</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s2">"</span><span class="s">--namespace=app"</span>
    <span class="pi">-</span> <span class="s2">"</span><span class="s">--resource=deployment"</span>
    <span class="pi">-</span> <span class="s2">"</span><span class="s">--name=postgres"</span>
    <span class="pi">-</span> <span class="s2">"</span><span class="s">--condition=available"</span>
    <span class="na">serviceAccountName</span><span class="pi">:</span> <span class="s">go-wait-for-k8s-sa</span>
</code></pre></div></div>

<p>This setup ensures that <code class="language-plaintext highlighter-rouge">go_wait_for_k8s</code> can only access the necessary Kubernetes resources, adhering to the principle of least privilege.</p>

<h3 id="tldr-a-simple-init-container-to-wati-for-your-resources">TLDR: a simple init container to wati for your resources</h3>

<p><code class="language-plaintext highlighter-rouge">go_wait_for_k8s</code> is a simple yet powerful tool designed to enhance the reliability of your Kubernetes deployments. By running as an <code class="language-plaintext highlighter-rouge">InitContainer</code>, it ensures that critical dependencies are ready before your application starts, reducing the risk of failed starts and other issues related to unavailable resources.</p>

<p>Whether you’re managing databases, services, or any other type of Kubernetes resource, <code class="language-plaintext highlighter-rouge">go_wait_for_k8s</code> provides a streamlined way to handle readiness checks natively within your Kubernetes environment. With minimal setup, you can make your applications more resilient and your deployments more predictable.</p>

<p>For more information, to see the source code, or to contribute, visit the <a href="https://github.com/jhoelzel/go_wait_for_k8s">GitHub repository</a>. As always, feedback and contributions are welcome. Let’s keep making Kubernetes a more robust platform, one InitContainer at a time.</p>]]></content><author><name>Johannes Hölzel</name></author><category term="kubernetes" /><category term="automation" /><category term="cli" /><category term="kubernetes-probes" /><summary type="html"><![CDATA[In Kubernetes, ensuring that dependent services are ready before your application starts can be a critical task. For instance, your application might rely on a PostgreSQL database, and you need to make sure the database is fully initialized and ready to accept connections before the app itself starts. Handling this properly often requires custom scripts or tools to manage readiness checks, which can get complex and error-prone.]]></summary></entry><entry><title type="html">Kuberntes Access Proxies</title><link href="https://www.hoelzel.it/kubernetes/2024/09/01/k8s-access-proxy.html" rel="alternate" type="text/html" title="Kuberntes Access Proxies" /><published>2024-09-01T00:00:00+00:00</published><updated>2024-09-01T00:00:00+00:00</updated><id>https://www.hoelzel.it/kubernetes/2024/09/01/k8s-access-proxy</id><content type="html" xml:base="https://www.hoelzel.it/kubernetes/2024/09/01/k8s-access-proxy.html"><![CDATA[<p>Kubernetes has revolutionized the way we manage and deploy applications by providing powerful tools for orchestrating containers at scale. However, as your Kubernetes environment grows, so does the complexity of managing access. Kubernetes’ native Role-Based Access Control (RBAC) is essential but insufficient on its own to address the challenges of scale, security, and compliance. This is where an access proxy, such as Teleport, becomes indispensable.</p>

<p>This post will explore why using an access proxy is critical for your Kubernetes deployment, discuss the importance of robust RBAC from the outset, and provide an in-depth look at alternatives like <strong>authentik</strong> and others, helping you choose the right tool for your environment.</p>

<hr />

<h3 id="the-complexity-of-kubernetes-access-management">The Complexity of Kubernetes Access Management</h3>

<p>When you start with Kubernetes, managing access seems manageable. Kubernetes offers RBAC, allowing you to define permissions for users and service accounts to perform specific actions. However, as you add more teams, services, and layers of abstraction, the complexity increases exponentially.</p>

<h4 id="challenges-of-rbac-management-at-scale">Challenges of RBAC Management at Scale</h4>

<p>RBAC in Kubernetes allows for fine-grained access control, but it comes with significant challenges:</p>

<ul>
  <li>
    <p><strong>Complexity of Policies:</strong> As the number of users and roles grows, maintaining and auditing RBAC policies manually becomes a Herculean task. Misconfigurations are common, leading to either overly permissive roles that introduce security risks or overly restrictive ones that hinder productivity.</p>
  </li>
  <li>
    <p><strong>Lack of Visibility:</strong> Without proper tools, it’s challenging to track who has access to what resources, when they accessed them, and what actions they performed. This lack of visibility can lead to unauthorized access going unnoticed.</p>
  </li>
  <li>
    <p><strong>Operational Overhead:</strong> As the environment scales, the manual effort required to manage RBAC increases, leading to higher operational overhead and potential errors.</p>
  </li>
</ul>

<p>In large environments, these challenges make it clear that relying solely on native RBAC is not sustainable. This is where an access proxy comes into play.</p>

<hr />

<h3 id="what-is-an-access-proxy-and-why-you-need-one">What Is an Access Proxy and Why You Need One</h3>

<p>An access proxy acts as a centralized gateway that manages, secures, and logs all access to your Kubernetes clusters and other infrastructure. By funneling all access through this proxy, you gain better control, enhanced security, and detailed auditing capabilities.</p>

<h4 id="centralized-access-management">Centralized Access Management</h4>

<p>One of the key advantages of an access proxy like Teleport is centralized access management. With a single point of control, you can enforce consistent security policies across your infrastructure, whether for SSH access, Kubernetes clusters, databases, or internal web applications.</p>

<ul>
  <li>
    <p><strong>Consistency:</strong> Centralized management ensures that security policies are applied uniformly across all environments, reducing the risk of misconfigurations.</p>
  </li>
  <li>
    <p><strong>Efficiency:</strong> It simplifies the administration of access controls, making it easier to manage who has access to what without needing to touch each individual component separately.</p>
  </li>
  <li>
    <p><strong>Reduced Attack Surface:</strong> By centralizing access, you minimize the number of entry points an attacker could exploit, effectively reducing your overall attack surface.</p>
  </li>
</ul>

<h4 id="compliance-and-auditing-made-easy">Compliance and Auditing Made Easy</h4>

<p>Compliance with regulations like GDPR, HIPAA, and SOC 2 requires detailed records of access to sensitive data. An access proxy simplifies this by automatically logging all access attempts and actions, providing a comprehensive audit trail that can be used for compliance reporting.</p>

<ul>
  <li>
    <p><strong>Audit Logs:</strong> Detailed, immutable logs of every access attempt and action provide the visibility needed to demonstrate compliance during audits.</p>
  </li>
  <li>
    <p><strong>Session Recording:</strong> Advanced features like session recording allow you to capture and replay sessions, providing a forensic tool for investigating suspicious activity and proving compliance.</p>
  </li>
</ul>

<hr />

<h3 id="teleport-a-deep-dive-into-its-features">Teleport: A Deep Dive into Its Features</h3>

<p>Teleport is a popular choice as an access proxy for Kubernetes due to its robust feature set tailored to cloud-native environments. Here’s what makes it stand out:</p>

<h4 id="unified-access-management">Unified Access Management</h4>

<p>Teleport offers unified access management across various protocols and services:</p>

<ul>
  <li>
    <p><strong>SSH and Kubernetes Access:</strong> Manage SSH access and Kubernetes API access with the same set of RBAC policies, ensuring consistency across different parts of your infrastructure.</p>
  </li>
  <li>
    <p><strong>Database Access:</strong> Extend your access management to databases, allowing centralized control over who can access and modify data, with detailed logging of all interactions.</p>
  </li>
  <li>
    <p><strong>Web Application Access:</strong> Use Teleport to manage access to internal web applications, ensuring only authorized users can access critical tools and dashboards.</p>
  </li>
</ul>

<h4 id="comprehensive-compliance-features">Comprehensive Compliance Features</h4>

<p>Teleport’s compliance features are designed to meet the needs of highly regulated industries:</p>

<ul>
  <li>
    <p><strong>Detailed Audit Logs:</strong> Logs every access attempt, whether successful or not, and every command executed, providing an auditable trail for compliance and security.</p>
  </li>
  <li>
    <p><strong>Session Recording:</strong> Capture entire user sessions, which can be replayed to review actions taken, crucial for both security investigations and compliance audits.</p>
  </li>
  <li>
    <p><strong>Integration with SIEMs:</strong> Teleport integrates with popular Security Information and Event Management (SIEM) tools, allowing you to aggregate logs and correlate them with other security data for comprehensive monitoring.</p>
  </li>
</ul>

<h4 id="easy-integration-and-deployment">Easy Integration and Deployment</h4>

<p>Teleport is designed for easy deployment and integration into existing environments:</p>

<ul>
  <li>
    <p><strong>Helm Charts:</strong> Official Helm charts allow for straightforward deployment within Kubernetes, enabling rapid deployment and scaling of Teleport.</p>
  </li>
  <li>
    <p><strong>API and CI/CD Integration:</strong> Teleport provides a robust API, allowing integration with existing CI/CD pipelines, ensuring that access controls are enforced throughout your development lifecycle.</p>
  </li>
</ul>

<hr />

<h3 id="alternatives-to-teleport-a-closer-look">Alternatives to Teleport: A Closer Look</h3>

<p>While Teleport is a powerful solution, there are other tools that might be a better fit depending on your specific needs. Let’s explore <strong>authentik</strong> and other alternatives.</p>

<h4 id="authentik">Authentik</h4>

<p><strong>Overview:</strong> Authentik is an open-source identity provider (IdP) designed to offer maximum flexibility and integration capabilities. While it’s primarily an identity provider, it also functions effectively as an access management tool in Kubernetes environments.</p>

<p><strong>Key Features:</strong></p>
<ul>
  <li>
    <p><strong>Identity Provider (IdP):</strong> Authentik serves as a central identity provider, supporting Single Sign-On (SSO) across a wide range of applications and services.</p>
  </li>
  <li>
    <p><strong>Multi-Factor Authentication (MFA):</strong> Supports MFA, enhancing the security of access to critical resources.</p>
  </li>
  <li>
    <p><strong>Custom Workflows:</strong> Authentik stands out for its ability to implement custom authentication and authorization workflows, giving administrators the flexibility to tailor the solution to their specific needs.</p>
  </li>
  <li>
    <p><strong>Protocol Support:</strong> Supports all major authentication protocols, including OAuth2, SAML, LDAP, and SCIM, making it highly versatile for integration with various services and applications.</p>
  </li>
</ul>

<p><strong>Use Cases:</strong> Authentik is ideal for organizations looking for an open-source, highly customizable identity management solution that integrates well with existing infrastructure, including Kubernetes. Its ability to adapt to complex environments makes it a strong alternative for those who need more than what traditional access proxies offer.</p>

<h4 id="boundary-by-hashicorp">Boundary by HashiCorp</h4>

<p><strong>Overview:</strong> Boundary is a tool by HashiCorp that provides secure, identity-based access management, particularly focused on dynamic cloud environments.</p>

<p><strong>Key Features:</strong></p>
<ul>
  <li>
    <p><strong>Fine-Grained Access Controls:</strong> Boundary provides strict identity-based access controls, ensuring that only authenticated users can access sensitive systems.</p>
  </li>
  <li>
    <p><strong>Session Logging:</strong> Offers comprehensive session logging and auditing capabilities, essential for compliance and security monitoring.</p>
  </li>
  <li>
    <p><strong>Seamless Integration:</strong> Works seamlessly with other HashiCorp tools, such as Vault for secrets management, making it a good fit for organizations already using the HashiCorp stack.</p>
  </li>
</ul>

<p><strong>Use Cases:</strong> Boundary is well-suited for organizations that require secure, auditable remote access to critical infrastructure, particularly those already using HashiCorp tools.</p>

<h4 id="strongdm">StrongDM</h4>

<p><strong>Overview:</strong> StrongDM offers a unified approach to managing access across databases, servers, and Kubernetes clusters, with a strong focus on ease of use and comprehensive auditing.</p>

<p><strong>Key Features:</strong></p>
<ul>
  <li>
    <p><strong>Centralized Access Management:</strong> StrongDM centralizes access control across various systems, providing a single interface for managing permissions.</p>
  </li>
  <li>
    <p><strong>Automated Auditing:</strong> Automatically logs all access activities, creating a complete audit trail that is essential for compliance.</p>
  </li>
  <li>
    <p><strong>Ease of Use:</strong> Known for its user-friendly interface, StrongDM simplifies the management of access across multiple platforms, making it accessible even to non-experts.</p>
  </li>
</ul>

<p><strong>Use Cases:</strong> StrongDM is particularly beneficial for organizations that need a unified access management solution that is easy to deploy and use, with strong compliance features.</p>

<hr />

<h3 id="what-does-it-all-mean">What does it all mean?</h3>

<p>As your Kubernetes environment grows, so does the complexity of managing access. Relying solely on Kubernetes’ native RBAC can leave you vulnerable to security risks and compliance issues. Implementing an access proxy like Teleport, authentik, or another solution is essential for centralizing access control, enhancing security, and ensuring compliance.</p>

<p>Teleport offers a comprehensive solution with features tailored for cloud-native environments, while authentik provides a flexible, open-source alternative with strong identity management capabilities. Boundary and StrongDM offer additional options, each with its own strengths, depending on your specific requirements.</p>

<p>Take the time to evaluate your current access management setup and consider integrating an access proxy to secure your Kubernetes environment. The peace of mind that comes from knowing your access controls are robust and compliant is invaluable.</p>

<hr />

<h3 id="further-readingreferences">Further Reading/References</h3>

<ul>
  <li><strong>Teleport Documentation:</strong> <a href="https://goteleport.com/docs/">Teleport Docs</a></li>
  <li><strong>authentik Documentation:</strong> <a href="https://docs.goauthentik.io/">authentik Docs</a></li>
  <li><strong>Boundary by HashiCorp:</strong> <a href="https://www.hashicorp.com/products/boundary">Boundary</a></li>
  <li><strong>StrongDM Overview:</strong> <a href="https://www.strongdm.com/features">StrongDM Features</a></li>
</ul>]]></content><author><name>Johannes Hölzel</name></author><category term="kubernetes" /><category term="teleport" /><category term="access proxy" /><category term="kubernetes access" /><summary type="html"><![CDATA[Kubernetes has revolutionized the way we manage and deploy applications by providing powerful tools for orchestrating containers at scale. However, as your Kubernetes environment grows, so does the complexity of managing access. Kubernetes’ native Role-Based Access Control (RBAC) is essential but insufficient on its own to address the challenges of scale, security, and compliance. This is where an access proxy, such as Teleport, becomes indispensable.]]></summary></entry><entry><title type="html">Streamlining Helm Chart Management with Argo Helm Versioner</title><link href="https://www.hoelzel.it/devops/2024/08/31/argo-helm-versioner.html" rel="alternate" type="text/html" title="Streamlining Helm Chart Management with Argo Helm Versioner" /><published>2024-08-31T00:00:00+00:00</published><updated>2024-08-31T00:00:00+00:00</updated><id>https://www.hoelzel.it/devops/2024/08/31/argo-helm-versioner</id><content type="html" xml:base="https://www.hoelzel.it/devops/2024/08/31/argo-helm-versioner.html"><![CDATA[<p>For anyone managing Kubernetes environments with Argo CD, keeping Helm charts up-to-date is a routine but essential task. Argo Helm Versioner is a straightforward tool that helps automate this process, ensuring your deployments stay current without adding extra overhead.</p>

<h3 id="why-helm-chart-versioning-can-be-a-headache">Why Helm Chart Versioning Can Be a Headache</h3>

<p>If you’ve ever found yourself manually checking which versions of Helm charts are running in your environment, you know how tedious and error-prone it can be. Argo Helm Versioner takes this off your plate by automatically scanning your project directories, identifying Argo CD applications that use Helm charts, and comparing the deployed versions with the latest available versions in the Helm repositories.</p>

<h3 id="streamlining-your-devops-workflow-with-argo-helm-versioner">Streamlining Your DevOps Workflow with Argo Helm Versioner</h3>

<p>Argo Helm Versioner integrates seamlessly into your existing workflows. Whether you’re running it as part of your CI/CD pipeline, performing regular checks, or just doing a one-off audit, this tool simplifies the process:</p>

<ul>
  <li><strong>Deep Directory Scanning</strong>: No more manual hunting. The tool finds every relevant YAML file, even if it’s buried deep in your directory structure.</li>
  <li><strong>Accurate Version Comparison</strong>: By leveraging semantic versioning, Argo Helm Versioner ensures that even minor differences between versions are detected.</li>
  <li><strong>Clear Output</strong>: The tool presents results in an easy-to-read table, showing you at a glance which applications need an update.</li>
</ul>

<h3 id="real-world-example-using-argo-helm-versioner-with-popular-helm-charts">Real-World Example: Using Argo Helm Versioner with Popular Helm Charts</h3>

<p>Let’s explore how this might look with some widely-used Helm charts. Suppose you’re managing a Kubernetes environment that includes several popular services:</p>

<ul>
  <li><strong>Nginx Ingress Controller</strong>: A crucial component for managing external access to your services.</li>
  <li><strong>Prometheus</strong>: The go-to solution for monitoring and alerting.</li>
  <li><strong>Grafana</strong>: Paired with Prometheus for powerful data visualization.</li>
  <li><strong>Redis</strong>: Often used as a caching layer or message broker.</li>
  <li><strong>ElasticSearch</strong>: Commonly deployed for search and analytics.</li>
</ul>

<p>Here’s what the output might look like after running Argo Helm Versioner:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Application             FilePath                                    Current Version   Latest Version   Status
nginx-ingress           /apps/nginx-ingress/argo-app.yaml           4.0.6             4.3.0            Update available
prometheus              /apps/prometheus/argo-app.yaml              14.8.0            15.4.0           Update available
grafana                 /apps/grafana/argo-app.yaml                 6.17.4            7.2.0            Update available
redis                   /apps/redis/argo-app.yaml                   15.3.1            15.4.0           Up-to-date
elasticsearch           /apps/elasticsearch/argo-app.yaml           7.10.1            7.12.1           Update available
</code></pre></div></div>

<p>In this scenario:</p>

<ul>
  <li>The <code class="language-plaintext highlighter-rouge">nginx-ingress</code>, <code class="language-plaintext highlighter-rouge">prometheus</code>, <code class="language-plaintext highlighter-rouge">grafana</code>, and <code class="language-plaintext highlighter-rouge">elasticsearch</code> services all have newer versions available, prompting you to review and potentially update these applications.</li>
  <li>The <code class="language-plaintext highlighter-rouge">redis</code> service is up-to-date, giving you peace of mind that no action is needed there.</li>
</ul>

<p>This output gives you a clear, concise overview of the status of your Helm charts, enabling you to quickly address any outdated services.</p>

<h3 id="customizing-and-extending-argo-helm-versioner">Customizing and Extending Argo Helm Versioner</h3>

<p>While Argo Helm Versioner is designed to be simple, it’s also flexible. You can easily integrate it with other tools in your pipeline or extend its functionality to suit your specific needs. Whether you want to automate updates, trigger notifications, or just keep a clean report of your Helm chart versions, this tool has you covered.</p>

<h3 id="practical-use-cases-a-handy-tool-for-daily-operations">Practical Use Cases: A Handy Tool for Daily Operations</h3>

<p>Imagine you’re managing a microservices architecture with several teams. Argo Helm Versioner can help ensure that all your services are using the most current and secure versions of Helm charts. It’s not about solving world problems-it’s about making your life a bit easier by automating a routine task that, left unchecked, could lead to issues down the road.</p>

<h3 id="conclusion-a-simple-tool-for-a-simple-task">Conclusion: A Simple Tool for a Simple Task</h3>

<p>Argo Helm Versioner is exactly what it sounds like: a tool to help you keep your Helm chart versions in check. It’s not trying to be more than it is. It’s a practical, no-frills solution for those who want to automate version checks and avoid the headache of manual updates. If you’re using Helm and Argo CD, it’s worth a look. For more information and to get started, check out the <a href="https://github.com/jhoelzel/argo-helm-versioner/tree/main">Argo Helm Versioner GitHub repository</a>. Contributions and feedback are welcome as the tool continues to evolve based on real-world needs.</p>]]></content><author><name>Johannes Hölzel</name></author><category term="devops" /><category term="argo" /><category term="helm" /><category term="kubernetes" /><category term="ci/cd" /><summary type="html"><![CDATA[For anyone managing Kubernetes environments with Argo CD, keeping Helm charts up-to-date is a routine but essential task. Argo Helm Versioner is a straightforward tool that helps automate this process, ensuring your deployments stay current without adding extra overhead.]]></summary></entry><entry><title type="html">Demystifying etcd</title><link href="https://www.hoelzel.it/kubernetes/2023/05/08/what-is-etcd.html" rel="alternate" type="text/html" title="Demystifying etcd" /><published>2023-05-08T00:00:00+00:00</published><updated>2023-05-08T00:00:00+00:00</updated><id>https://www.hoelzel.it/kubernetes/2023/05/08/what-is-etcd</id><content type="html" xml:base="https://www.hoelzel.it/kubernetes/2023/05/08/what-is-etcd.html"><![CDATA[<p>By now, you are no strangers to the challenges of managing distributed systems. Enter etcd(<code class="language-plaintext highlighter-rouge">pronounced /ˈɛtsiːdiː/,</code>), the unsung hero that quietly toils behind the scenes to keep these systems in check. <strong>etcd is an open source distributed key-value store tailor-made to hold and administer the crucial information that distributed systems</strong>, such as Kubernetes, need to function smoothly.</p>

<p>At the heart of distributed systems lie multiple machines working in harmony to achieve a shared objective. Kubernetes utilizes etcd as its reliable data store, providing a consistent view of the system’s status, including clusters, pods, and running application instances etc. If etcd were to pen an autobiography, it might be titled, <strong>“From Linux Directory to Distributed Data Store: My Journey.”</strong> Because the name “etcd” originates from a naming convention within the Linux directory structure. In UNIX, all system configuration files for a single system are contained in a folder called “/etc,” and the “d” in “etcd” stands for “distributed.”</p>

<p>But <em>why etcd</em>, you ask?</p>

<p>This versatile tool is:</p>

<ul>
  <li>Fully replicated: Every node has access to the <em>entire data store</em>, ensuring data availability and redundancy.</li>
  <li>Highly available: With no single point of failure, etcd <em>can gracefully handle hardware failures</em> and network partitions.</li>
  <li>Reliably consistent: Every data read returns the most recent write, avoiding conflicts or discrepancies.</li>
  <li>Fast: etcd boasts a benchmark of 10,000 writes per second.</li>
  <li>Secure: Automatic TLS, optional SSL client certificate authentication, and role-based access controls protect sensitive data.</li>
  <li>Simple: Applications can interact with etcd using standard HTTP/JSON tools.</li>
</ul>

<p>One might say that etcd is like a high-performance engine and it works best with fast storage disks (SSDs are highly recommended).</p>

<h2 id="built-on-the-raft-consensus-algorithm">Built on the Raft consensus algorithm</h2>

<p>Etcd ensures data store consistency across all nodes in a cluster, maintaining highly available and consistently replicated copies of the data store. Raft elects a leader node that manages replication for the followers, creating a harmonious and efficient system.</p>

<p>The leader accepts requests from clients and forwards them to follower nodes. <strong>The leader verifies that a majority of follower nodes have stored each new request and only then applies the entry to its local state machine</strong>. If this is not the case, the leader will retry until all followers have stored all log entries consistently. This helps the system using it by providing an equal query surface to all processes that seek the information, not matter which node they query. Furthermore if a follower node fails to receive a message from the leader within a specified time interval, an election is held to choose a new leader automatically. It declares itself a candidate, and the other followers vote for it or any other one based on their availability.</p>

<p>In the worst case scenario, where the majority of etcd nodes fail, the cluster will not accept any more writes to it and will maintain its state. The cluster can only recover from a majority failure once the majority of members become available and reach concensious. If they can not, then the operator must start disaster recovery to recover the cluster by themselves.</p>

<p>In the realm of Kubernetes, etcd plays a vital role as the primary key-value store for cluster state data. Kubernetes leverages etcd’s “watch” function to monitor data and reconfigure itself when changes arise. This allows Kubernetes to maintain an ideal state and quickly respond to any discrepancies. <em>The “watch” function stores values representing the actual and ideal state of the cluster</em> and can initiate a response when they diverge. This is the little magic, that lies in every kubernetes cluster and what makes them work in the first place.</p>

<h2 id="etcd-vs-redis">Etcd VS Redis</h2>

<p>Now, let’s address the elephant in the room: etcd vs. Redis. While both are open source tools, their primary functions differ significantly. Redis is an in-memory data store that excels in speed, but etcd trades off speed for greater reliability and guaranteed consistency, making it the go-to choice for storing and managing distributed system configuration data. Redis supports a wider variety of data types and structures than etcd and has much faster read/write performance. However, etcd has superior fault tolerance, stronger failover, and continuous data availability capabilities. <em>Most importantly, etcd persists all stored data to disk</em>, trading off speed for greater reliability and guaranteed consistency.</p>

<h2 id="etcd-vs-sql">Etcd VS SQL</h2>

<p>And what about etcd vs. MySQL and PostgreSQL? While all three are data storage solutions, etcd is a distributed key-value store specifically designed for distributed systems, while MySQL and PostgreSQL are traditional relational databases adept at handling structured data with relationships. Choosing the right data storage solution is like selecting the perfect pair of shoes: it depends on the occasion, or in this case, the specific requirements of your system.</p>

<p>In a nutshell, etcd is a steadfast sentinel keeping distributed systems, like Kubernetes, in harmony. Its design prioritizes fully replicated, highly available, reliable, fast, secure, and simple data storage. With its superior fault tolerance and data persistence capabilities, etcd is the ideal candidate for storing and managing distributed system configuration information, standing steadfast in its duty to maintain the state of distributed systems.</p>

<p>Traditional databases like MySQL and PostgreSQL prioritize consistency and durability, but their fault tolerance capabilities depend on the specific setup and configuration. While etcd is ideal for managing critical information in distributed systems, MySQL and PostgreSQL are versatile relational databases that handle structured data with relationships.</p>

<blockquote>
  <p>NOTE: Of course you can also store cluster state in a relational database, <a href="k3s.io">K3S</a> will happily do that for you, but once you actually get it up and running your MySQL setup will literally be overtaken by state queries. I have tried on many occasions to make it work, and an effective system WILL require absolute finetuning from your end.</p>
</blockquote>

<h2 id="etcds-ecosystem-and-extensibility">Etcd’s Ecosystem and Extensibility</h2>

<p>etcd’s importance extends beyond Kubernetes, as it can also be utilized to coordinate critical system and metadata across clusters of various distributed applications. Its simplicity and consistency make it an attractive choice for any distributed system that requires a reliable data store.</p>

<p>Its API enables seamless integration with a myriad of applications, and developers can easily interact with it using popular programming languages like Go, Python, Java, and more. Moreover, etcd’s extensible architecture allows it to act as a foundation for building custom distributed systems tailored to specific use cases and requirements.</p>

<blockquote>
  <p>NOTE: While you definitly can, its not recommended to modify the state of a kubernetes cluster directly, whitout using the API.</p>
</blockquote>

<p>The etcd community continues to grow and evolve, contributing to the development and enhancement of it. A plethora of third-party tools, libraries, and frameworks have emerged to simplify the deployment, management, and monitoring of etcd clusters. Some notable tools in the etcd ecosystem include:</p>

<p><em><a href="https://github.com/etcd-io/etcd/tree/main/etcdctl">etcdctl</a></em>: A command-line client for managing etcd.
<em><a href="https://github.com/openshift/cluster-etcd-operator">etcd-operator</a></em>: A Kubernetes operator that automates etcd cluster management tasks.
<em><a href="https://github.com/kubernetes-sigs/etcdadm">etcdadm</a></em>: a command-line tool for operating an etcd cluster. It makes it easy to create a new cluster, add a member to, or remove a member from an existing cluster. Its user experience is inspired by kubeadm.
<em><a href="https://github.com/giantswarm/etcd-backup-operator">etcd-backup-operator</a></em> takes backups of ETCD instances on both the control plane and tenant clusters.
<em><a href="https://github.com/gtamas/etcdmanager">ETCD Manager</a></em> Provide an efficient, modern GUI for desktop</p>

<p>A more extensive list of tools and libraries can be found in the (etcd docs itself](https://etcd.io/docs/v3.5/integrations/]</p>

<h2 id="things-to-consider-when-deploying-and-managing-etcd">Things to consider when Deploying and Managing etcd</h2>

<p>As custodians of complex distributed systems, engineers need to understand the importance of deploying and managing etcd with care. Adhering to best practices is essential to ensure the health and performance of an etcd cluster. Some key recommendations include:</p>

<p><strong>Hardware considerations</strong>: Use SSDs for storage, as etcd’s performance relies heavily on disk speed. Provide adequate memory and CPU resources to accommodate the cluster’s workloads.
<strong>Configuration</strong>: Tune etcd’s configuration parameters to optimize performance for your specific use case, considering factors like network latency and data size.
<strong>Monitoring and alerting</strong>: Implement monitoring solutions, such as Prometheus and Grafana, to track etcd performance metrics and set up alerts for potential issues.
<strong>Backup and recovery</strong>: Regularly create and test backups of your etcd data to ensure swift recovery in the event of data loss or corruption.
<strong>Security</strong>: Implement role-based access controls, TLS, and SSL client certificate authentication to protect sensitive configuration data and restrict access to authorized personnel only.</p>

<h2 id="in-conclusion">In Conclusion</h2>

<p>etcd is a robust and reliable distributed key-value store that quietly underpins distributed systems like Kubernetes. Its fully replicated, highly available, reliable, fast, secure, and simple design ensures that it remains a steadfast guardian of critical information in distributed systems. With its unique advantages over Redis, MySQL, and PostgreSQL in the realm of distributed system configuration management, etcd has earned its place as an essential tool for distributed systems.</p>

<p>As the etcd ecosystem continues to flourish, and best practices for its deployment and management are widely adopted, the future looks promising for this unsung hero. With etcd, distributed systems can thrive and achieve their full potential, all while maintaining harmony and consistency in the face of ever-growing complexity.</p>

<h3 id="sources">Sources</h3>
<ul>
  <li><a href="https://etcd.io/docs/v3.5/">etcd docs</a></li>
  <li><a href="https://etcd.io/docs/v3.5/dev-guide/">Developer Guide</a></li>
  <li><a href="https://etcd.io/docs/v3.5/op-guide/">Operation Guide</a></li>
</ul>]]></content><author><name>Johannes Hölzel</name></author><category term="kubernetes" /><category term="kubernetes" /><category term="kubernetes-state" /><category term="etcd" /><summary type="html"><![CDATA[By now, you are no strangers to the challenges of managing distributed systems. Enter etcd(pronounced /ˈɛtsiːdiː/,), the unsung hero that quietly toils behind the scenes to keep these systems in check. etcd is an open source distributed key-value store tailor-made to hold and administer the crucial information that distributed systems, such as Kubernetes, need to function smoothly.]]></summary></entry><entry><title type="html">Fixing a Kubernetes Namespace Stuck in Terminating State</title><link href="https://www.hoelzel.it/kubernetes/2023/05/04/fix-stuck-namespaces.html" rel="alternate" type="text/html" title="Fixing a Kubernetes Namespace Stuck in Terminating State" /><published>2023-05-04T00:00:00+00:00</published><updated>2023-05-04T00:00:00+00:00</updated><id>https://www.hoelzel.it/kubernetes/2023/05/04/fix-stuck-namespaces</id><content type="html" xml:base="https://www.hoelzel.it/kubernetes/2023/05/04/fix-stuck-namespaces.html"><![CDATA[<p>Kubernetes Namespaces play a crucial role in managing resources within a Kubernetes cluster. They allow for the organization and isolation of resources, enabling efficient management of large-scale applications. However, occasionally, Kubernetes Namespaces can become stuck in the Terminating state, preventing us from creating new resources or updating existing ones. In this article, I will delve into various potential causes for this behavior and outline some strategies, for you to overcome these obstacles.</p>

<h2 id="diagnosing-and-troubleshooting-stuck-namespaces">Diagnosing and Troubleshooting Stuck Namespaces</h2>

<p>Before diving into the potential causes, it is essential to diagnose and troubleshoot stuck Namespaces using Kubernetes events and logs. Common patterns and error messages may simply present you with the underlying issues.</p>

<p>The first step is always to retrieve events within the Namespace:</p>

<pre><code class="language-SH">kubectl get events -n &lt;namespace_name&gt;
</code></pre>

<p>Once you have identified an Issue, you can review logs of related resources, such as Pods, to identify potential issues:</p>

<pre><code class="language-SH">kubectl logs -n &lt;namespace_name&gt; &lt;pod_name&gt; [&lt;container_name&gt;]
# to view the logs of all containers of a pod
kubectl logs -n &lt;namespace_name&gt; &lt;pod_name&gt; --all-containers
# to follow the logs in real-time use
kubectl logs -n &lt;namespace_name&gt; &lt;pod_name&gt; [&lt;container_name&gt;] --follow
# for already deleted container logs use 
kubectl logs -n &lt;namespace_name&gt; &lt;pod_name&gt; [&lt;container_name&gt;] --previous

</code></pre>

<h2 id="cause-1-finalizers">Cause 1: Finalizers</h2>

<p>Finalizers are an essential aspect of the Kubernetes resource lifecycle, ensuring that specific actions occur before an object is deleted. These actions might include releasing external resources, cleaning up associated data, or notifying other components of the deletion. Kubernetes uses finalizers to implement graceful deletion for resources, allowing them to complete any required cleanup tasks before the object is permanently removed.</p>

<p>However, finalizers can sometimes prevent a Namespace from being deleted if the required cleanup tasks are not completed or if a custom finalizer is implemented incorrectly. In such cases, it’s crucial to identify and remove finalizers within the Namespace to allow for its smooth deletion. Custom finalizers or non-standard resources, such as Custom Resource Definitions (CRDs), might also be preventing the Namespace from being deleted.</p>

<p>In order to identify and address finalizer-related issues in a Namespace:</p>

<ul>
  <li>List the Namespace’s configuration in JSON format to find existing finalizers:</li>
</ul>

<pre><code class="language-SH">kubectl get namespace &lt;namespace_name&gt; -o json
</code></pre>

<ul>
  <li>examine the finalizers field under metadata to identify any finalizers associated with the Namespace.</li>
</ul>

<p>If the finalizers appear to be standard Kubernetes finalizers, investigate the Namespace’s resources and their statuses to identify any issues preventing the finalizers from completing their tasks.</p>

<p>As for custom or non-standard finalizers, review the associated components or controllers responsible for implementing the finalizers. Ensure they are functioning correctly and completing their intended tasks. Fix any issues or misconfigurations that might be preventing the finalizers from completing.</p>

<ul>
  <li>If the finalizers are still causing issues, you can consider removing them manually as a last resort. Be aware that manually removing finalizers can result in incomplete cleanup tasks, potentially leaving orphaned resources or data:</li>
</ul>

<pre><code class="language-SH">kubectl patch namespace &lt;namespace_name&gt; -p '{"metadata":{"finalizers":[]}}' --type=merge
</code></pre>

<p>This command will remove all finalizers from the Namespace, allowing it to be deleted. However, use this approach cautiously and only when necessary, as it bypasses the standard deletion process.</p>

<h2 id="cause-2-admission-webhooks">Cause 2: Admission Webhooks</h2>

<p>Admission webhooks, including both mutating and validating types, play a vital role in the Kubernetes API request processing pipeline. These webhooks intercept and modify or validate requests, ensuring they meet specific criteria before being processed further. However, they can sometimes interfere with the deletion of resources and cause a Namespace to become stuck in the Terminating state. To address this issue, follow these steps:</p>

<ul>
  <li>List the configured admission webhooks in your cluster to identify any that might impact the Namespace you are trying to delete:</li>
</ul>

<pre><code class="language-SH">kubectl get mutatingwebhookconfigurations,validatingwebhookconfigurations
</code></pre>

<ul>
  <li>
    <p>Examine the listed webhooks and their configurations to determine if any of them are preventing the deletion of resources within the Namespace. For example, a webhook might be configured with a rule that prevents the deletion of specific resources or returns an error upon deletion attempts.</p>
  </li>
  <li>
    <p>Temporarily disable or modify the problematic webhook configuration to allow the Namespace to be deleted. Be cautious when disabling webhooks, as it may have unintended side effects on other resources in the cluster.</p>
  </li>
</ul>

<p>For instance, to disable a validating webhook temporarily, you can edit its configuration:</p>

<pre><code class="language-SH">kubectl edit validatingwebhookconfigurations &lt;webhook_configuration_name&gt;
</code></pre>

<ul>
  <li>Set the <strong>failurePolicy</strong> to <em>Ignore</em> and save the changes. This action will cause Kubernetes to ignore any errors returned by the webhook and proceed with the deletion process.</li>
</ul>

<blockquote>
  <p>NOTE: After resolving the issue and successfully deleting the Namespace, <strong>remember to re-enable or restore the webhook configuration</strong> to its original state to maintain the intended functionality of your cluster.</p>
</blockquote>

<p>By identifying and addressing any issues related to admission webhooks, you can efficiently resolve Namespace deletion problems and ensure a smooth Kubernetes experience. <strong>Keep in mind that modifying webhook configurations <em>will</em> have broader implications on your cluster, so exercise caution and thoroughly understand the impact of any changes before implementing them.</strong></p>

<h2 id="cause-3-cluster-stability-and-health">Cause 3: Cluster Stability and Health</h2>

<p>In some cases, a Namespace may be stuck due to underlying issues with the Kubernetes cluster itself. To ensure the cluster is healthy:</p>

<ul>
  <li>Check the health of the cluster components:</li>
</ul>

<pre><code class="language-SH">kubectl get componentstatuses
</code></pre>

<p>A healthy output should look like this:</p>
<pre><code class="language-SH">Warning: v1 ComponentStatus is deprecated in v1.19+
NAME                 STATUS    MESSAGE   ERROR
scheduler            Healthy   ok        
controller-manager   Healthy   ok   
</code></pre>
<p>Investigate and resolve any unhealthy component statuses before attempting to delete the Namespace. For example, if the etcd component is unhealthy, you may need to troubleshoot and repair the etcd cluster.</p>

<p>For Kubernetes versions v1.19 and above, since componentstatuses is deprecated, you can use the following command:</p>

<pre><code class="language-SH">kubectl get --raw='/readyz?verbose'
</code></pre>

<p>A healthy output should look like this:</p>
<pre><code class="language-SH">[+]ping ok
[+]log ok
[+]etcd ok
[+]etcd-readiness ok
[+]informer-sync ok
[+]poststarthook/start-kube-apiserver-admission-initializer ok
[+]poststarthook/generic-apiserver-start-informers ok
[+]poststarthook/priority-and-fairness-config-consumer ok
[+]poststarthook/priority-and-fairness-filter ok
[+]poststarthook/storage-object-count-tracker-hook ok
[+]poststarthook/start-apiextensions-informers ok
[+]poststarthook/start-apiextensions-controllers ok
[+]poststarthook/crd-informer-synced ok
[+]poststarthook/bootstrap-controller ok
[+]poststarthook/rbac/bootstrap-roles ok
[+]poststarthook/scheduling/bootstrap-system-priority-classes ok
[+]poststarthook/priority-and-fairness-config-producer ok
[+]poststarthook/start-cluster-authentication-info-controller ok
[+]poststarthook/start-kube-apiserver-identity-lease-controller ok
[+]poststarthook/start-kube-apiserver-identity-lease-garbage-collector ok
[+]poststarthook/start-legacy-token-tracking-controller ok
[+]poststarthook/aggregator-reload-proxy-client-cert ok
[+]poststarthook/start-kube-aggregator-informers ok
[+]poststarthook/apiservice-registration-controller ok
[+]poststarthook/apiservice-status-available-controller ok
[+]poststarthook/kube-apiserver-autoregistration ok
[+]autoregister-completion ok
[+]poststarthook/apiservice-openapi-controller ok
[+]poststarthook/apiservice-openapiv3-controller ok
[+]shutdown ok
readyz check passed
</code></pre>

<ul>
  <li>
    <p>Investigate and resolve any unhealthy component statuses before attempting to delete the Namespace. Each component might have unique troubleshooting steps depending on the issue. For example, if the etcd component is unhealthy, you may need to troubleshoot and repair the etcd cluster.</p>
  </li>
  <li>
    <p>Ensure that the Kubernetes control plane components, such as the API server, controller manager, and scheduler, are running correctly. Verify that there are no error messages or issues in the logs of these components.</p>
  </li>
  <li>
    <p>Check the health of the worker nodes in your cluster:</p>
  </li>
</ul>

<pre><code class="language-SH">kubectl get nodes -o wide
NAME     STATUS   ROLES                  AGE   VERSION        INTERNAL-IP     EXTERNAL-IP   OS-IMAGE                           KERNEL-VERSION                      CONTAINER-RUNTIME
ultron   Ready    control-plane,master   23d   v1.26.3+k3s1   172.17.66.235   &lt;none&gt;        Rancher Desktop WSL Distribution   5.15.90.1-microsoft-standard-WSL2   docker://20.10.21    
</code></pre>

<p>Investigate and resolve any issues with nodes that are in a <em>NotReady</em> state or have other issues.</p>

<ul>
  <li>If all of the above yields nothing, verify that the kubelet service is running properly on each node by checking its logs and status.</li>
</ul>

<p>By thoroughly examining the health and stability of your Kubernetes cluster, you can identify and resolve issues that might be causing a Namespace to become stuck in the Terminating state. Ensuring your cluster’s overall health will not only resolve Namespace deletion problems but also contribute to a smooth and efficient Kubernetes experience.</p>

<h2 id="cause-4-custom-resource-definitions-crds-and-namespaces">Cause 4: Custom Resource Definitions (CRDs) and Namespaces</h2>

<p>Custom Resource Definitions (CRDs) can play a role in Namespaces being stuck in the Terminating state. CRDs extend the Kubernetes API by defining new resource types, which may affect the deletion process. To handle CRDs in the context of stuck Namespaces:</p>

<p>List all CRDs in your cluster:</p>

<pre><code class="language-SH">kubectl get crds
</code></pre>
<p>Inspect the CRDs to determine if any are associated with the Namespace you are trying to delete.</p>

<p>Delete any associated CRD instances within the Namespace:</p>

<pre><code class="language-SH">kubectl delete &lt;custom_resource_name&gt;.&lt;api_group&gt; -n &lt;namespace_name&gt; 
</code></pre>
<p>If necessary, remove the CRD itself after deleting its instances:</p>

<pre><code class="language-SH">kubectl delete crd &lt;custom_resource_name&gt;.&lt;api_group&gt;
</code></pre>

<h2 id="cause-5-resource-quotas-and-stuck-namespaces">Cause 5: Resource Quotas and Stuck Namespaces</h2>

<p>Resource quotas can also impact Namespaces getting stuck in the Terminating state. Quotas limit the amount of resources that can be consumed within a Namespace, and exceeding these limits may cause issues.</p>

<p>Check the resource quotas for the Namespace:</p>

<pre><code class="language-SH">kubectl get resourcequotas -n &lt;namespace_name&gt;
</code></pre>

<p>If the resource usage is exceeding the quota, you may need to adjust the quota or reduce resource consumption within the Namespace.</p>

<p>To update the resource quota, edit the ResourceQuota object:</p>

<pre><code class="language-SH">kubectl edit resourcequota &lt;resource_quota_name&gt; -n &lt;namespace_name&gt;
</code></pre>

<p>Adjust the limits as needed, then save and exit the editor.</p>

<h2 id="the-namespace-deletion-process">The Namespace Deletion Process</h2>

<p>After identifying and addressing the potential cause(s) behind the stuck Namespace, proceed with the Namespace deletion process:</p>

<p>Eliminate all resources within the Namespace:</p>

<pre><code class="language-SH">kubectl delete all --all -n &lt;namespace_name&gt;
</code></pre>

<p>Force Namespace deletion:</p>

<p>The kubectl delete namespace command offers additional parameters to help you tailor the deletion process to your specific requirements:</p>

<ul>
  <li>
    <p>–grace-period: This parameter sets the duration in seconds that the system should wait before forcefully deleting the Namespace. By setting the grace period to 0 (–grace-period=0), you instruct Kubernetes to skip the waiting period and immediately proceed with the deletion.</p>
  </li>
  <li>
    <p>–force: The –force parameter ensures that the Namespace is deleted forcefully, bypassing the default graceful deletion process. This can be helpful in cases where the Namespace is stuck in the Terminating state due to various issues discussed earlier.</p>
  </li>
</ul>

<pre><code class="language-SH">kubectl delete namespace &lt;namespace_name&gt; --grace-period=0 --force
</code></pre>

<p>Reevaluate the Namespace status:</p>

<pre><code class="language-SH">kubectl get namespaces
</code></pre>

<p>Regenerate the Namespace, if necessary:</p>

<pre><code class="language-SH">kubectl create namespace &lt;namespace_name&gt;
</code></pre>

<p>By understanding and addressing these potential causes, senior engineers can effectively resolve a Namespace stuck in the Terminating state.</p>

<p>There you have it, these comprehensive strategies will enable you to enjoy a more efficient and smoother Kubernetes experience, ensuring optimal performance and reliability in their container orchestration system.</p>]]></content><author><name>Johannes Hölzel</name></author><category term="kubernetes" /><category term="kubernetes" /><category term="kubernetes-namespaces" /><category term="kubernetes-problems" /><summary type="html"><![CDATA[Kubernetes Namespaces play a crucial role in managing resources within a Kubernetes cluster. They allow for the organization and isolation of resources, enabling efficient management of large-scale applications. However, occasionally, Kubernetes Namespaces can become stuck in the Terminating state, preventing us from creating new resources or updating existing ones. In this article, I will delve into various potential causes for this behavior and outline some strategies, for you to overcome these obstacles.]]></summary></entry><entry><title type="html">Kubernetes Headless Services</title><link href="https://www.hoelzel.it/kubernetes/2023/05/01/Headless-Services.html" rel="alternate" type="text/html" title="Kubernetes Headless Services" /><published>2023-05-01T00:00:00+00:00</published><updated>2023-05-01T00:00:00+00:00</updated><id>https://www.hoelzel.it/kubernetes/2023/05/01/Headless-Services</id><content type="html" xml:base="https://www.hoelzel.it/kubernetes/2023/05/01/Headless-Services.html"><![CDATA[<p><strong>Kubernetes services</strong> are abstractions that define a logical set of pods and a policy to access them. Services enable loose coupling between pods and <strong>provide a stable IP address</strong>, DNS name, and load balancing for distributing network traffic.</p>

<p><strong>Headless Services do not provide a stable IP address</strong> are a special type of Kubernetes service that allow for more direct control over pod-to-pod communication and service discovery, particularly useful for stateful applications and custom load balancing scenarios.</p>

<h2 id="what-are-kubernetes-headless-services">What are Kubernetes Headless Services?</h2>

<p>Simply said, a headless service is a Kubernetes <strong>service without a cluster IP</strong>, allowing pods to be directly reached via their individual IP addresses. This facilitates direct communication between pods and bypasses the default load balancing provided by Kubernetes.</p>

<p>Unlike ClusterIP services, which provide a single IP address that load balances traffic to the backend pods, headless services expose each pod’s IP address directly. This is useful when pods need to be individually addressed or when custom load balancing is required, or you simply want the endpoints of your service listed without providing access.</p>

<p>The primary usecases for headless services are:</p>

<ol>
  <li>
    <p><strong>Stateful applications</strong>: Headless services are essential for stateful applications that require stable network identities and direct communication between instances, such as databases and message brokers.</p>
  </li>
  <li>
    <p><strong>Service discovery</strong>: Headless services enable custom service discovery mechanisms, allowing applications to discover and communicate with individual pod instances.</p>
  </li>
  <li>
    <p><strong>Custom load balancing</strong>: By exposing individual pod IPs, headless services allow for the implementation of custom load balancing strategies tailored to specific application requirements.</p>
  </li>
</ol>

<h2 id="headless-services-with-a-regular-deployment">Headless Services with a regular Deployment</h2>

<p>First lets create a deployment for our Headless Service. They are primarily used with StatefulSets but in order to demostrae why we will start with a deployment.</p>

<p>This YAML file creates a deployment for a sample application with 3 replicas.</p>

<pre><code class="language-YAML">apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app-container
        image: busybox:latest
        args:
        - /bin/sh
        - -c
        - while true; do { echo -e 'HTTP/1.1 200 OK\n\nHello from Busybox!'; } | nc -l -p 80; done
        ports:
        - containerPort: 80
</code></pre>

<p>Now let’s create the corresponding headless service:</p>

<pre><code class="language-YAML">apiVersion: v1
kind: Service
metadata:
  name: my-headless-service
spec:
  # Set clusterIP to None to create a headless service
  clusterIP: None
  # Selector to match the desired set of pods
  selector:
    app: my-app
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80
</code></pre>

<p>As you can see, there is no magic and the only real difference between a headless and a “normal” service is, that the headless service will not provision a clusterIP for you.</p>

<p>Apply the YAML configuration using <code class="language-plaintext highlighter-rouge">kubectl apply -f &lt;filename.yaml&gt;</code> to create the headless service.</p>

<pre><code class="language-SH">$ kubectl apply -f ./example.yaml
deployment.apps/my-app-deployment created
service/my-headless-service created     
</code></pre>

<p>Verify the creation of the headless service using <code class="language-plaintext highlighter-rouge">kubectl get services</code> and ensure that the ‘CLUSTER-IP’ field is set to ‘None’.</p>

<pre><code class="language-SH">$ kubectl get service my-headless-service -o wide
NAME                  TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE   SELECTOR
my-headless-service   ClusterIP   None         &lt;none&gt;        80/TCP    90s   app=my-app
</code></pre>

<p>If you want to list all endpoints of your service you can simply do so with <code class="language-plaintext highlighter-rouge">kubectl get endpoints &lt;myservice&gt;</code> which will look something like this:</p>

<pre><code class="language-SH">$ kubectl get endpoints my-headless-service
NAME                  ENDPOINTS                                            AGE
my-headless-service   10.42.0.124:80,10.42.0.125:80,10.42.0.126:80   4m7s
</code></pre>

<p>Or if you like some more specific information you can output them as Json like this:</p>

<pre><code class="language-SH">$ kubectl get endpoints my-headless-service --output=json
{
    "apiVersion": "v1",
    "kind": "Endpoints",
    "metadata": {
        "annotations": {
            "endpoints.kubernetes.io/last-change-trigger-time": "2023-05-02T08:58:35Z"
        },
        "creationTimestamp": "2023-05-02T08:58:20Z",
        "labels": {
            "service.kubernetes.io/headless": ""
        },
        "name": "my-headless-service",
        "namespace": "default",
        "resourceVersion": "169091",
        "uid": "c431ad71-40e8-4140-987f-654e596e957d"
    },
    "subsets": [
        {
            "addresses": [
                {
                    "ip": "10.42.0.137",
                    "nodeName": "ultron",
                    "targetRef": {
                        "kind": "Pod",
                        "name": "my-app-deployment-6694968687-c6rv9",
                        "namespace": "default",
                        "uid": "32a435fb-7938-4a6d-a4c0-ad0439a9dabe"
                    }
                },
                {
                    "ip": "10.42.0.138",
                    "nodeName": "ultron",
                    "targetRef": {
                        "kind": "Pod",
                        "name": "my-app-deployment-6694968687-9djtn",
                        "namespace": "default",
                        "uid": "e45f0d0e-292e-408c-99a1-ecdc7ccaa3cc"
                    }
                },
                {
                    "ip": "10.42.0.139",
                    "nodeName": "ultron",
                    "targetRef": {
                        "kind": "Pod",
                        "name": "my-app-deployment-6694968687-ln985",
                        "namespace": "default",
                        "uid": "fd77afaf-8b5f-4994-9e12-c9ba780ce44f"
                    }
                }
            ],
            "ports": [
                {
                    "name": "http",
                    "port": 80,
                    "protocol": "TCP"
                }
            ]
        }
    ]
}
</code></pre>

<h3 id="discovering-pods-with-dns">Discovering pods with DNS</h3>
<p>Now let us create a dnsutils container in the same namespace. With it, we can perform an dig command to query the A records for the headless service</p>
<pre><code class="language-SH">$ kubectl run -i --tty dnsutils --image=tutum/dnsutils --restart=Never -- sh
If you don't see a command prompt, try pressing enter.
# 
dig my-headless-service.default.svc.cluster.local A

; &lt;&lt;&gt;&gt; DiG 9.9.5-3ubuntu0.2-Ubuntu &lt;&lt;&gt;&gt; my-headless-service.default.svc.cluster.local A
;; global options: +cmd
;; Got answer:
;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: NOERROR, id: 20692
;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;my-headless-service.default.svc.cluster.local. IN A

;; ANSWER SECTION:
my-headless-service.default.svc.cluster.local. 5 IN A 10.42.0.137
my-headless-service.default.svc.cluster.local. 5 IN A 10.42.0.139
my-headless-service.default.svc.cluster.local. 5 IN A 10.42.0.138

;; Query time: 0 msec
;; SERVER: 10.43.0.10#53(10.43.0.10)
;; WHEN: Tue May 02 08:55:22 U
</code></pre>

<p>As you can see in the Answer Section, all IPs of the replicas have been nicely returned to us. This will help us connect directly through them, however the hostname remains the same.
Most DNS query programs will not make sure that you rotate the IP and will only connect the first one and provide no load balancing whatsoever.</p>

<h3 id="using-srv-records-for-service-discovery">Using SRV records for service discovery</h3>

<p>SRV records are a type of DNS record that provides additional information beyond just the IP address of a service. They are particularly useful for service discovery within a cluster, allowing applications to obtain information about available pod instances and their respective ports.</p>

<p>An SRV record is a DNS record with the format: <code class="language-plaintext highlighter-rouge">_service._protocol.&lt;service_name&gt;.&lt;namespace&gt;.svc.cluster.local</code>. It contains information about the service’s hostname, port, protocol, and priority. Applications can perform DNS lookups using the SRV record to discover the available pod instances and their corresponding ports, allowing them to establish connections and communicate with individual pods.</p>

<p>This is especially useful for stateful applications that require direct pod-to-pod communication, as well as for custom load balancing scenarios where the built-in Kubernetes load balancing is not sufficient.</p>

<p>In order to check this out we first need to create a dnsutils container in the same namespace. Next, we can perform an dig command to query the SRV records for the headless service:</p>

<pre><code class="language-SH">$ kubectl run -i --tty dnsutils --image=tutum/dnsutils --restart=Never -- sh
If you don't see a command prompt, try pressing enter.
# 
dig _http._tcp.my-headless-service.default.svc.cluster.local SRV

; &lt;&lt;&gt;&gt; DiG 9.9.5-3ubuntu0.2-Ubuntu &lt;&lt;&gt;&gt; _http._tcp.my-headless-service.default.svc.cluster.local SRV
;; global options: +cmd
;; Got answer:
;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: NOERROR, id: 61360
;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 4
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;_http._tcp.my-headless-service.default.svc.cluster.local. IN SRV

;; ANSWER SECTION:
_http._tcp.my-headless-service.default.svc.cluster.local. 5 IN SRV 0 33 80 10-42-0-138.my-headless-service.default.svc.cluster.local.
_http._tcp.my-headless-service.default.svc.cluster.local. 5 IN SRV 0 33 80 10-42-0-137.my-headless-service.default.svc.cluster.local.
_http._tcp.my-headless-service.default.svc.cluster.local. 5 IN SRV 0 33 80 10-42-0-139.my-headless-service.default.svc.cluster.local.

;; ADDITIONAL SECTION:
10-42-0-137.my-headless-service.default.svc.cluster.local. 5 IN A 10.42.0.137
10-42-0-139.my-headless-service.default.svc.cluster.local. 5 IN A 10.42.0.139
10-42-0-138.my-headless-service.default.svc.cluster.local. 5 IN A 10.42.0.138

;; Query time: 0 msec
;; SERVER: 10.43.0.10#53(10.43.0.10)
;; WHEN: Tue May 02 08:58:58 UTC 2023
;; MSG SIZE  rcvd: 703
</code></pre>

<blockquote>
  <p>NOTE: Headless Services for deployments automatically create DNS records for each pod, following the pattern <code class="language-plaintext highlighter-rouge">&lt;podip&gt;.&lt;service_name&gt;.&lt;namespace&gt;.svc.cluster.local</code>.</p>
</blockquote>

<p>Now using this information, lets try it out by spinning up a busybox and running wget:</p>

<pre><code class="language-SH">$ kubectl run -i --tty busybox --image=busybox --restart=Never -- sh 
If you don't see a command prompt, try pressing enter.
/ # wget -O- http://10-42-0-138.my-headless-service.default.svc.cluster.local:80
Connecting to 10-42-0-138.my-headless-service.default.svc.cluster.local:80 (10.42.0.138:80)
writing to stdout
Hello from Busybox!
-                    100% |*****************************************************************************************************************************************************************************************************************************|    20  0:00:00 ETA
written to stdout
</code></pre>

<p>As you can see the dns name of the pod is using the <em>pod ip as dns hostnamme</em> and thus it is not going to be stable across new iterations of the pod or even scaling the deployment up or down.
Let us find out how the same scenario will play out with a StatefullSet:</p>

<h2 id="using-a-satefullset">Using a SatefullSet</h2>

<p>Now that we have seen what happens to a deployment, lets check what happens if we change it to a StatefullSet.</p>

<p>In order to that we have to change our yaml like this:</p>

<pre><code class="language-YAML">apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: my-app-statefulset
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  serviceName: my-headless-service
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app-container
        image: busybox:latest
        args:
        - /bin/sh
        - -c
        - while true; do { echo -e 'HTTP/1.1 200 OK\n\nHello from Busybox!'; } | nc -l -p 80; done
        ports:
        - containerPort: 80
        readinessProbe:
          tcpSocket:
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 10
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi
---
apiVersion: v1
kind: Service
metadata:
  name: my-headless-service
spec:
  # Set clusterIP to None to create a headless service
  clusterIP: None
  # Selector to match the desired set of pods
  selector:
    app: my-app
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80

</code></pre>

<p>As you may see, I have changed Deployment to StatefulSet and added a serviceName field in the spec section that points to the headless service we have defined. We have also added a volumeClaimTemplates field to the spec section, which defines a persistent volume claim template that can be used by the StatefulSet’s pods to store data.</p>

<p>The StatefulSet maintains a unique identity for each of its pods and ensures that they are created in a predictable order. It is useful when you need to manage stateful applications, like databases, where each instance requires a unique identity and persistent storage.</p>

<pre><code class="language-SH">kubectl run -i --tty dnsutils --image=tutum/dnsutils --restart=Never -- sh
If you don't see a command prompt, try pressing enter.
dig my-headless-service.default.svc.cluster.local A

; &lt;&lt;&gt;&gt; DiG 9.9.5-3ubuntu0.2-Ubuntu &lt;&lt;&gt;&gt; my-headless-service.default.svc.cluster.local A
;; global options: +cmd
;; Got answer:
;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: NOERROR, id: 18912
;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;my-headless-service.default.svc.cluster.local. IN A

;; ANSWER SECTION:
my-headless-service.default.svc.cluster.local. 5 IN A 10.42.0.148
my-headless-service.default.svc.cluster.local. 5 IN A 10.42.0.143
my-headless-service.default.svc.cluster.local. 5 IN A 10.42.0.146

;; Query time: 0 msec
;; SERVER: 10.43.0.10#53(10.43.0.10)
;; WHEN: Tue May 02 15:00:11 UTC 2023
;; MSG SIZE  rcvd: 257
</code></pre>

<p>So far the Answer section looks pretty much the same as for the deployment, but watch what happens if we quere the SRV record:</p>

<pre><code class="language-SH"># 
dig _http._tcp.my-headless-service.default.svc.cluster.local SRV

; &lt;&lt;&gt;&gt; DiG 9.9.5-3ubuntu0.2-Ubuntu &lt;&lt;&gt;&gt; _http._tcp.my-headless-service.default.svc.cluster.local SRV
;; global options: +cmd
;; Got answer:
;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: NOERROR, id: 12868
;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 4
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;_http._tcp.my-headless-service.default.svc.cluster.local. IN SRV

;; ANSWER SECTION:
_http._tcp.my-headless-service.default.svc.cluster.local. 5 IN SRV 0 33 80 my-app-statefulset-0.my-headless-service.default.svc.cluster.local.
_http._tcp.my-headless-service.default.svc.cluster.local. 5 IN SRV 0 33 80 my-app-statefulset-1.my-headless-service.default.svc.cluster.local.
_http._tcp.my-headless-service.default.svc.cluster.local. 5 IN SRV 0 33 80 my-app-statefulset-2.my-headless-service.default.svc.cluster.local.

;; ADDITIONAL SECTION:
my-app-statefulset-1.my-headless-service.default.svc.cluster.local. 5 IN A 10.42.0.146
my-app-statefulset-0.my-headless-service.default.svc.cluster.local. 5 IN A 10.42.0.143
my-app-statefulset-2.my-headless-service.default.svc.cluster.local. 5 IN A 10.42.0.148

;; Query time: 0 msec
;; SERVER: 10.43.0.10#53(10.43.0.10)
;; WHEN: Tue May 02 15:01:52 UTC 2023
;; MSG SIZE  rcvd: 757
</code></pre>

<blockquote>
  <p>NOTE: Headless services automatically create DNS records for each pod, following the pattern <code class="language-plaintext highlighter-rouge">&lt;hostname&gt;.&lt;service_name&gt;.&lt;namespace&gt;.svc.cluster.local</code>.</p>
</blockquote>

<h3 id="why-headless-services-work-best-with-statefullsets">Why headless services work best with StatefullSets</h3>

<p>When a StatefulSet is created, Kubernetes assigns a unique identity to each of its pods, in the form of an ordinal index. The SRV records returned by the dig command reflect this by including the ordinal index in the hostname of each pod. For example, my-app-statefulset-0 refers to the first pod in the StatefulSet.</p>

<p>They are predominantly used with StatefulSets because they enable DNS-based service discovery for each individual pod, using the unique hostname assigned by Kubernetes in a stable manner. This allows applications running inside the pods to easily discover and connect to other pods in the StatefulSet, using their unique identities. This <strong>stable network identity</strong> is crucial for stateful applications that require a consistent identity for data replication, leader election, or other distributed systems tasks.</p>

<p>While it is possible to use a headless service with a Deployment, some of the advantages of using a headless service with a StatefulSet do not apply to a Deployment. Here’s why:</p>

<ol>
  <li>
    <p>Stable network identity: In a StatefulSet, each pod gets a unique and stable hostname based on the StatefulSet’s name and an index (e.g., my-app-statefulset-0, my-app-statefulset-1). Deployments, on the other hand, create pods with random hashes in their names (e.g., my-app-deployment-6f8d6b94c7). Thus, pods created by a Deployment do not have stable hostnames, which makes it difficult to maintain a consistent network identity for stateful applications that rely on it for data replication, leader election, or other tasks. Also as we have seen, the DNS Hostname of a deployed pod will be its IP.</p>
  </li>
  <li>
    <p>Direct access to individual pods: While headless services can still provide direct access to individual pods in a Deployment, the lack of a stable network identity makes it harder to manage communication between specific instances or replicas. Without stable hostnames, clients need to rely on other mechanisms, such as labels or annotations, to identify and communicate with specific instances. This adds complexity to the system and might not be suitable for certain stateful applications. Of course you can query the SRV entry in many iterations, but you need to make sure that your application takes care of it.</p>
  </li>
  <li>
    <p>Ordered and controlled scaling: StatefulSets provide ordered and controlled scaling, ensuring that pods are created and terminated in a specific order (based on their index). This is particularly important for stateful applications where the order of scaling operations can affect data consistency or availability. Deployments, however, scale pods without any specific order, which could lead to issues in stateful applications.</p>
  </li>
  <li>
    <p>Persistent storage: StatefulSets can leverage Stateful Volumes, which ensures that each pod gets its own unique and persistent storage. When a pod is rescheduled, <em>it retains the same storage</em>, allowing for seamless data recovery. Deployments do not have this native capability, making it challenging to manage stateful applications that require persistent storage.</p>
  </li>
</ol>

<p>Therefore, while headless services can be used with both StatefulSets and Deployments, the advantages of using a headless service with a StatefulSet do not apply to Deployments because of the lack of stable network identity, unordered scaling, and absence of native persistent storage support.</p>

<h3 id="finishing-up">Finishing up</h3>

<p>Kubernetes headless services provide a powerful way to manage direct pod-to-pod communication, service discovery, and custom load balancing. They are particularly useful for stateful applications and situations where the default load balancing provided by Kubernetes is insufficient and are an important tool in a developer’s arsenal.</p>]]></content><author><name>Johannes Hölzel</name></author><category term="kubernetes" /><category term="kubernetes" /><category term="kubernetes-services" /><category term="kubernetes-headless-services" /><summary type="html"><![CDATA[Kubernetes services are abstractions that define a logical set of pods and a policy to access them. Services enable loose coupling between pods and provide a stable IP address, DNS name, and load balancing for distributing network traffic.]]></summary></entry><entry><title type="html">Embracing the Kubernetes Downward API</title><link href="https://www.hoelzel.it/kubernetes/2023/04/25/Pod-info-mounted.html" rel="alternate" type="text/html" title="Embracing the Kubernetes Downward API" /><published>2023-04-25T00:00:00+00:00</published><updated>2023-04-25T00:00:00+00:00</updated><id>https://www.hoelzel.it/kubernetes/2023/04/25/Pod-info-mounted</id><content type="html" xml:base="https://www.hoelzel.it/kubernetes/2023/04/25/Pod-info-mounted.html"><![CDATA[<p>In the realm of Kubernetes, it is sometimes important to expose certain Pod and container fields to containers running within the Pod. 
These approaches, collectively known as the <em>downward API</em>, enable developers to harness the power of Pod and container fields within their containers.</p>

<p>The Kubernetes Downward API is an essential mechanism that facilitates containers in a Kubernetes cluster to obtain metadata regarding themselves or the environment in which they operate, which enables us to make more solid decisions. It enables a container to gather information about its configuration and additional metadata. This information is valuable for generating configuration files, implementing context-aware behavior, or conducting monitoring.</p>

<p>The Downward API can be employed through two methods:</p>

<p><strong>Environment Variables</strong>: By utilizing the env or envFrom field in the container’s specification, pod or container metadata can be injected into a container’s environment variables. The metadata is exposed as environment variables and can be accessed by the applications operating within the container.</p>

<p><strong>Volumes</strong>: The Downward API can also populate files within a volume. This is achieved using a DownwardAPIVolume, a Kubernetes volume type capable of exposing pod metadata as files in a directory. The volume can subsequently be mounted into the container’s filesystem at a specified path.</p>

<p>Today I am going to make sure that you understand both and can use them with confidence in your deployments. But first I want to make clear that it saidly <strong>does not support all the fields in the podspec</strong>.
This is a detailed list of fields that can be used with the it and the methods they are available through (Environment variables, Volume files, or both):</p>

<ol>
  <li>metadata.name (Both)
    <ul>
      <li>The name of the pod.</li>
    </ul>
  </li>
  <li>metadata.namespace (Both)
    <ul>
      <li>The namespace the pod is running in.</li>
    </ul>
  </li>
  <li>metadata.labels (Volume files)
    <ul>
      <li>A set of key-value pairs (labels) attached to the pod.</li>
    </ul>
  </li>
  <li>metadata.annotations (Volume files)
    <ul>
      <li>A set of key-value pairs (annotations) providing additional non-identifying information about the pod.</li>
    </ul>
  </li>
  <li>status.podIP (Environment variables)
    <ul>
      <li>The IP address of the pod.</li>
    </ul>
  </li>
  <li>spec.nodeName (Environment variables)
    <ul>
      <li>The name of the node the pod is running on.</li>
    </ul>
  </li>
  <li>status.hostIP (Environment variables)
    <ul>
      <li>The IP address of the node where the pod is running.</li>
    </ul>
  </li>
  <li>spec.serviceAccountName (Environment variables)
    <ul>
      <li>The name of the service account associated with the pod.</li>
    </ul>
  </li>
  <li>metadata.uid (Environment variables)
    <ul>
      <li>The unique identifier (UID) assigned to the pod by the Kubernetes system.</li>
    </ul>
  </li>
  <li>resources.requests.cpu (Environment variables)
    <ul>
      <li>The amount of CPU requested by the container.</li>
    </ul>
  </li>
  <li>resources.requests.memory (Environment variables)
    <ul>
      <li>The amount of memory requested by the container.</li>
    </ul>
  </li>
  <li>resources.limits.cpu (Environment variables)
    <ul>
      <li>The maximum amount of CPU allowed for the container.</li>
    </ul>
  </li>
  <li>resources.limits.memory (Environment variables)
    <ul>
      <li>The maximum amount of memory allowed for the container.</li>
    </ul>
  </li>
</ol>

<h2 id="using-the-downwards-api-with-volumes">Using the downwards API with volumes</h2>

<blockquote>
  <p>Note: the downwards api for volumes currently is pretty limited. The only supported values are: “metadata.annotations”, “metadata.labels”, “metadata.name”, “metadata.namespace”, “metadata.uid”</p>
</blockquote>

<p>In order to expose the pods labels and annotations, I will create a Pod with a single container and project Pod-level fields into the running container as files. The Pod manifest provided below reveals a <code class="language-plaintext highlighter-rouge">downwardAPI</code> volume, which the container mounts at <code class="language-plaintext highlighter-rouge">/etc/downwardAPI</code>:</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">learning-about-downwardapi</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">zone</span><span class="pi">:</span> <span class="s">DE-Falkenstein</span>
    <span class="na">cluster</span><span class="pi">:</span> <span class="s">test-meadow</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">downwardAPI</span>
    <span class="na">team</span><span class="pi">:</span> <span class="s">devops</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">demo-container</span>
      <span class="na">image</span><span class="pi">:</span> <span class="s">registry.k8s.io/busybox</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="s">while </span><span class="no">true</span><span class="s">; do</span>
          <span class="s">if [[ -e /etc/downwardapi/labels ]]; then</span>
            <span class="s">echo -en 'Labels:\n'; cat /etc/downwardapi/labels; fi;</span>
          <span class="s">if [[ -e /etc/downwardapi/annotations ]]; then</span>
            <span class="s">echo -en '\nAnnotations:\n'; cat /etc/downwardapi/annotations; fi;</span>
          <span class="s">sleep 5;</span>
        <span class="s">done;</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">downwardapi</span>
          <span class="na">mountPath</span><span class="pi">:</span> <span class="s">/etc/downwardapi</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">downwardapi</span>
      <span class="na">downwardAPI</span><span class="pi">:</span>
        <span class="na">items</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s2">"</span><span class="s">labels"</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">metadata.labels</span>
          <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s2">"</span><span class="s">annotations"</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">metadata.annotations</span>
          <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s2">"</span><span class="s">podName"</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">metadata.name</span>
          <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Namespace"</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">metadata.namespace</span>              
</code></pre></div></div>

<h2 id="reviewing-the-container-definition">reviewing the container definition</h2>

<p>The main components are briefly described below:</p>

<ul>
  <li>
    <p>image: The container image is sourced from the registry.k8s.io repository, with the specific image being “busybox”, a lightweight and versatile Linux distribution. In this case it will help use outputting our stored information</p>
  </li>
  <li>
    <p>args: This is a list of arguments that will be passed to the shell command. In this case, it contains a single argument that is a multi-line shell script. The shell script does the following:
  a. It runs an infinite loop using the “while true” construct.
  b. Within the loop, it checks if the file “/etc/downwardapi/labels” exists by using the “-e” flag in the conditional expression. If it exists, it prints a newline character (‘\n’) twice and then the contents of the file using “cat”.
  c. Similarly, it checks if the file “/etc/downwardapi/annotations” exists, and if so, prints a newline character (‘\n’) twice and the contents of the file using “cat”.
  d. The loop then pauses for 5 seconds using the “sleep 5” command before reiterating through the process.</p>
  </li>
</ul>

<p>Therefore, we define a Pod called “learning-about-downwardapi” that runs a BusyBox image and executes a shell script, which continuously checks for the existence of two files (/etc/downwardapi/labels and /etc/downwardapi/annotations), and prints their contents if they exist, before pausing for 5 seconds and repeating the process.
We can observe that the Pod features a downwardsAPI Volume, and the container kindly mounts the volume at /etc/downwardsapi as files, so the pod can access the information.
The first element represents that the value of the Pod’s metadata.labels field should be saved in a file called ‘labels’. 
The second element proposes that the value of the Pod’s annotations field should be stored in a file named ‘annotations’.</p>

<h2 id="applying-and-testing">applying and testing</h2>

<p>Now that we have prepared our pod definition, lets apply it with:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl apply <span class="nt">-f</span> ./downwardsapitest.yaml 
</code></pre></div></div>
<p>And since I have not provided any namespace, it will be in the default namespace and we can retrieve the logs with:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl logs learning-about-downwardapi
</code></pre></div></div>

<p>and our output looks something like this:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Labels:
<span class="nv">cluster</span><span class="o">=</span><span class="s2">"test-meadow"</span>
<span class="nv">zone</span><span class="o">=</span><span class="s2">"DE-Falkenstein"</span>
Annotations:
<span class="nv">app</span><span class="o">=</span><span class="s2">"downwardAPI"</span>
kubectl.kubernetes.io/last-applied-configuration<span class="o">=</span><span class="s2">"{...}"</span>
kubernetes.io/config.seen<span class="o">=</span><span class="s2">"2023-04-25T07:29:39.669534852Z"</span>
kubernetes.io/config.source<span class="o">=</span><span class="s2">"api"</span>
</code></pre></div></div>

<p>As you can see, all of the labels and annotations have succesfully been mounted to the pod and we can access them through the container file system.
This is particularly  usefull, when your code makes decisions based on these parameters.</p>

<p>You also can simply exec the running pod itself to verify the files existance:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl <span class="nb">exec</span> <span class="nt">-it</span> learning-about-downwardapi <span class="nt">--</span> sh
</code></pre></div></div>

<p>where <em>cat</em> will give you the same results:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/# <span class="nb">cat</span> /etc/downwardapi/labels
<span class="nv">cluster</span><span class="o">=</span><span class="s2">"test-meadow"</span>
<span class="nv">zone</span><span class="o">=</span><span class="s2">"DE-Falkenstein"</span>/ <span class="c"># </span>
</code></pre></div></div>

<p>What is more interesting though is if the actual contents of the folder <em>/etc/downardsapi</em>! Let me show you by runing <em>ls</em>:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/# <span class="nb">cd</span> /etc/downwardapi/
/etc/downwardapi <span class="c"># ls -la</span>
total 4
drwxrwxrwt    3 root     root           120 Apr 25 07:37 <span class="nb">.</span>
drwxr-xr-x    1 root     root          4096 Apr 25 07:37 ..
drwxr-xr-x    2 root     root            80 Apr 25 07:37 ..2023_04_25_07_37_05.3522933447
lrwxrwxrwx    1 root     root            32 Apr 25 07:37 ..data -&gt; ..2023_04_25_07_37_05.3522933447
lrwxrwxrwx    1 root     root            18 Apr 25 07:37 annotations -&gt; ..data/annotations
lrwxrwxrwx    1 root     root            13 Apr 25 07:37 labels -&gt; ..data/labels
</code></pre></div></div>

<p>In the generated output, it is evident that both the labels and annotations files are located within a temporary subdirectory. In our specific example, the subdirectory is denoted as “..2023_04_25_07_37_05.3522933447”. Within the “/etc/downwardapi” directory, “..data” functions as a symbolic link that connects to the aforementioned temporary subdirectory. Additionally, within the same “/etc/downwardapi” directory, both the labels and annotations serve as symbolic links, so they can be updated!</p>

<p>Utilizing symbolic links facilitates dynamic and atomic updates of the metadata. This is achieved through writing updates to a new temporary directory, followed by an atomic update of the “..data” symlink employing the “rename(2)” system call. If you are used to deploying without kubernetes, you may remember this approach from countless release-deploy scripts out there.</p>

<p>Lets patch our pod with a new annotation to see how that works.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl patch pod learning-about-downwardapi <span class="nt">-p</span> <span class="s1">'{"metadata":{"annotations":{"updatestatus":"in-progress"}}}'</span>
pod/learning-about-downwardapi patched  
</code></pre></div></div>

<p>and we exec our running pod again with</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl <span class="nb">exec</span> <span class="nt">-it</span> learning-about-downwardapi <span class="nt">--</span> sh
</code></pre></div></div>

<p>and check out our mounted folder:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> /etc/downwardapi/
/etc/downwardapi <span class="c"># ls -la</span>
total 4
drwxrwxrwt    3 root     root           120 Apr 25 07:47 <span class="nb">.</span>
drwxr-xr-x    1 root     root          4096 Apr 25 07:37 ..
drwxr-xr-x    2 root     root            80 Apr 25 07:47 ..2023_04_25_07_47_52.1041351476
lrwxrwxrwx    1 root     root            32 Apr 25 07:47 ..data -&gt; ..2023_04_25_07_47_52.1041351476
lrwxrwxrwx    1 root     root            18 Apr 25 07:37 annotations -&gt; ..data/annotations
lrwxrwxrwx    1 root     root            13 Apr 25 07:37 labels -&gt; ..data/labels
</code></pre></div></div>

<p>As you can see the date has changed, so lets review our data:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/etc/downwardapi <span class="c"># cat annotations </span>
<span class="nv">app</span><span class="o">=</span><span class="s2">"downwardAPI"</span>
kubectl.kubernetes.io/last-applied-configuration<span class="o">=</span><span class="s2">"{...}"</span>
kubernetes.io/config.seen<span class="o">=</span><span class="s2">"2023-04-25T07:37:05.107154061Z"</span>
kubernetes.io/config.source<span class="o">=</span><span class="s2">"api"</span>
<span class="nv">team</span><span class="o">=</span><span class="s2">"devops"</span>
<span class="nv">updatestatus</span><span class="o">=</span><span class="s2">"in-progress"</span>
</code></pre></div></div>

<p>And there you have it, this way you can mount critical information directly into your pods and of course it does not stop at labels and annotations. You can mount the entire podspec this way.</p>

<h2 id="why-is-this-important">Why is this important</h2>

<p>Pods are considered immutable once they are created. If you need to change the environment variables for a running pod, you must create a new pod with the updated environment variables.
However <strong>Labels and annotations are indeed exceptions to the immutability of pods.</strong></p>

<p>Using this technique and code that uses for instance a filesystemwatcher, your program will be able to adapt to changes in annotations and labels which are currently widely used with operators or controllers.
<strong>There is a huge opportunity to have your pod react in real time to changes in the outside world, without actually stopping the container and ensuring downtime!</strong></p>

<h1 id="using-the-downwards-api-with-environment-variables">Using the Downwards API with Environment variables</h1>

<p>Environment variables are not as flexible as the mounted volumes, as I have demostrated in the last chapter, but they are still usefull for the information they contain.
<strong>You cannot directly patch environment variables in a running pod.</strong> If you need to change the environment variables for a running pod, you must create a new one with the updated environment variables and have to terminate the old one before.
In the case of a Deployment, StatefulSet, or DaemonSet, you can update the environment variables in the respective template spec, which will trigger a rolling update to replace the existing pods with new ones that have the updated environment variables.
They do however offer invaluable information as you can see in the example below:</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">learning-about-downwardapi</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">zone</span><span class="pi">:</span> <span class="s">DE-Falkenstein</span>
    <span class="na">cluster</span><span class="pi">:</span> <span class="s">test-meadow</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">downwardAPI</span>
    <span class="na">team</span><span class="pi">:</span> <span class="s">devops</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">demo-container</span>
      <span class="na">image</span><span class="pi">:</span> <span class="s">registry.k8s.io/busybox</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="s">while </span><span class="no">true</span><span class="s">; do</span>
          <span class="s">echo -en 'Pod Name:\n'; echo $POD_NAME;</span>
          <span class="s">echo -en '\nNamespace:\n'; echo $POD_NAMESPACE;</span>
          <span class="s">echo -en '\nPod IP:\n'; echo $POD_IP;</span>
          <span class="s">echo -en '\nNode Name:\n'; echo $NODE_NAME;</span>
          <span class="s">echo -en '\nHost IP:\n'; echo $HOST_IP;</span>
          <span class="s">echo -en '\nService Account Name:\n'; echo $SERVICE_ACCOUNT_NAME;</span>
          <span class="s">echo -en '\nUID:\n'; echo $POD_UID;</span>
          <span class="s">echo -en '\nCPU Request:\n'; echo $CPU_REQUEST;</span>
          <span class="s">echo -en '\nCPU Limit:\n'; echo $CPU_LIMIT;</span>
          <span class="s">echo -en '\nMemory Request:\n'; echo $MEMORY_REQUEST;</span>
          <span class="s">echo -en '\nMemory Limit:\n'; echo $MEMORY_LIMIT;</span>
          <span class="s">sleep 5;</span>
        <span class="s">done;</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">POD_NAME</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">metadata.name</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">POD_NAMESPACE</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">metadata.namespace</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">POD_IP</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">status.podIP</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">NODE_NAME</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">spec.nodeName</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">HOST_IP</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">status.hostIP</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">SERVICE_ACCOUNT_NAME</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">spec.serviceAccountName</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">POD_UID</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">metadata.uid</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">CPU_REQUEST</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">resourceFieldRef</span><span class="pi">:</span>
              <span class="na">containerName</span><span class="pi">:</span> <span class="s">demo-container</span>
              <span class="na">resource</span><span class="pi">:</span> <span class="s">requests.cpu</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">CPU_LIMIT</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">resourceFieldRef</span><span class="pi">:</span>
              <span class="na">containerName</span><span class="pi">:</span> <span class="s">demo-container</span>
              <span class="na">resource</span><span class="pi">:</span> <span class="s">limits.cpu</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">MEMORY_REQUEST</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">resourceFieldRef</span><span class="pi">:</span>
              <span class="na">containerName</span><span class="pi">:</span> <span class="s">demo-container</span>
              <span class="na">resource</span><span class="pi">:</span> <span class="s">requests.memory</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">MEMORY_LIMIT</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">resourceFieldRef</span><span class="pi">:</span>
              <span class="na">containerName</span><span class="pi">:</span> <span class="s">demo-container</span>
              <span class="na">resource</span><span class="pi">:</span> <span class="s">limits.memory</span>
      <span class="na">resources</span><span class="pi">:</span>
        <span class="na">requests</span><span class="pi">:</span>
          <span class="na">cpu</span><span class="pi">:</span> <span class="s">100m</span>
          <span class="na">memory</span><span class="pi">:</span> <span class="s">100Mi</span>
        <span class="na">limits</span><span class="pi">:</span>
          <span class="na">cpu</span><span class="pi">:</span> <span class="s">200m</span>
          <span class="na">memory</span><span class="pi">:</span> <span class="s">200Mi</span>


</code></pre></div></div>
<h1 id="mount-a-specific-annotation-as-environment-variable">mount a specific annotation as environment variable</h1>
<p>/E: 04.10.23</p>

<p>One of my readers pointed out, that it is indeed possible to mount a specific annotation as an environment variable into a pod by usin the fieldPath <code class="language-plaintext highlighter-rouge">metadata.annotations[yourAnnotation]</code> which will be plenty usefull for directly mounting an annotation.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>      env:
        - name: SOME_ANNOTATION
          valueFrom:
            fieldRef:
              fieldPath: metadata.annotations[k8s.io/some-annotation]
</code></pre></div></div>
<p><strong>However</strong> please be aware that, opposed to a volumemount, ENV variables will not be updated during runtime if you use it this way. So while it is indeed possible, please use it with care.</p>

<h1 id="combining-it-all-for-all-the-information">combining it all for all the information</h1>

<p>Finally lets combine the environment variables with the volumemounts and check all the information together. This does not change what we have learned, but gives us a complete overview of information we can read in the pod:</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">learning-about-downwardapi</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">zone</span><span class="pi">:</span> <span class="s">DE-Falkenstein</span>
    <span class="na">cluster</span><span class="pi">:</span> <span class="s">test-meadow</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">downwardAPI</span>
    <span class="na">team</span><span class="pi">:</span> <span class="s">devops</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">demo-container</span>
      <span class="na">image</span><span class="pi">:</span> <span class="s">registry.k8s.io/busybox</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="s">while </span><span class="no">true</span><span class="s">; do</span>
          <span class="s">echo -en 'Pod Name:\n'; echo $POD_NAME;</span>
          <span class="s">echo -en '\nNamespace:\n'; echo $POD_NAMESPACE;</span>
          <span class="s">echo -en '\nPod IP:\n'; echo $POD_IP;</span>
          <span class="s">echo -en '\nNode Name:\n'; echo $NODE_NAME;</span>
          <span class="s">echo -en '\nHost IP:\n'; echo $HOST_IP;</span>
          <span class="s">echo -en '\nService Account Name:\n'; echo $SERVICE_ACCOUNT_NAME;</span>
          <span class="s">echo -en '\nUID:\n'; echo $POD_UID;</span>
          <span class="s">echo -en '\nCPU Request:\n'; echo $CPU_REQUEST;</span>
          <span class="s">echo -en '\nCPU Limit:\n'; echo $CPU_LIMIT;</span>
          <span class="s">echo -en '\nMemory Request:\n'; echo $MEMORY_REQUEST;</span>
          <span class="s">echo -en '\nMemory Limit:\n'; echo $MEMORY_LIMIT;</span>
          <span class="s">if [[ -e /etc/downwardapi/labels ]]; then</span>
            <span class="s">echo -en '\nLabels:\n'; cat /etc/downwardapi/labels; fi;</span>
          <span class="s">if [[ -e /etc/downwardapi/annotations ]]; then</span>
            <span class="s">echo -en '\nAnnotations:\n'; cat /etc/downwardapi/annotations; fi;</span>
          <span class="s">sleep 5;</span>
        <span class="s">done;</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">POD_NAME</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">metadata.name</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">POD_NAMESPACE</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">metadata.namespace</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">POD_IP</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">status.podIP</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">NODE_NAME</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">spec.nodeName</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">HOST_IP</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">status.hostIP</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">SERVICE_ACCOUNT_NAME</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">spec.serviceAccountName</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">POD_UID</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">metadata.uid</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">CPU_REQUEST</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">resourceFieldRef</span><span class="pi">:</span>
              <span class="na">containerName</span><span class="pi">:</span> <span class="s">demo-container</span>
              <span class="na">resource</span><span class="pi">:</span> <span class="s">requests.cpu</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">CPU_LIMIT</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">resourceFieldRef</span><span class="pi">:</span>
              <span class="na">containerName</span><span class="pi">:</span> <span class="s">demo-container</span>
              <span class="na">resource</span><span class="pi">:</span> <span class="s">limits.cpu</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">MEMORY_REQUEST</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">resourceFieldRef</span><span class="pi">:</span>
              <span class="na">containerName</span><span class="pi">:</span> <span class="s">demo-container</span>
              <span class="na">resource</span><span class="pi">:</span> <span class="s">requests.memory</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">MEMORY_LIMIT</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">resourceFieldRef</span><span class="pi">:</span>
              <span class="na">containerName</span><span class="pi">:</span> <span class="s">demo-container</span>
              <span class="na">resource</span><span class="pi">:</span> <span class="s">limits.memory</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">downwardapi</span>
          <span class="na">mountPath</span><span class="pi">:</span> <span class="s">/etc/downwardapi</span>
      <span class="na">resources</span><span class="pi">:</span>
        <span class="na">requests</span><span class="pi">:</span>
          <span class="na">cpu</span><span class="pi">:</span> <span class="s">100m</span>
          <span class="na">memory</span><span class="pi">:</span> <span class="s">100Mi</span>
        <span class="na">limits</span><span class="pi">:</span>
          <span class="na">cpu</span><span class="pi">:</span> <span class="s">200m</span>
          <span class="na">memory</span><span class="pi">:</span> <span class="s">200Mi</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">downwardapi</span>
      <span class="na">downwardAPI</span><span class="pi">:</span>
        <span class="na">items</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s2">"</span><span class="s">labels"</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">metadata.labels</span>
          <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s2">"</span><span class="s">annotations"</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">metadata.annotations</span>
          <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s2">"</span><span class="s">podName"</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
              <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">metadata.name</span>
          <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Namespace"</span>
            <span class="na">fieldRef</span><span class="pi">:</span>
                <span class="na">fieldPath</span><span class="pi">:</span> <span class="s">metadata.namespace</span>
</code></pre></div></div>
<p>And let’s check out the example output:</p>

<pre><code class="language-SH">kubectl logs learning-about-downwardapi 
Pod Name:
learning-about-downwardapi

Namespace:
default

Pod IP:
10.42.0.112

Node Name:
ultron

Host IP:
172.17.66.235

Service Account Name:
default

UID:
afca6d22-a1df-4787-a947-bccf6b1015f8

CPU Request:
1

CPU Limit:
1

Memory Request:
104857600

Memory Limit:
209715200

Labels:
cluster="test-meadow"
zone="DE-Falkenstein"
Annotations:
app="downwardAPI"
kubectl.kubernetes.io/last-applied-configuration="{...}"
kubernetes.io/config.seen="2023-04-25T11:31:49.978886812Z"
kubernetes.io/config.source="api"
team="devops"
</code></pre>

<p>There you have it, extended information of the pod, directly mounted into it. The Kubernetes Downward API provides a powerful and flexible way to expose pod metadata and container resources to the containers running within a pod. By making use of environment variables and volume mounts, developers can access essential information about the pod, such as its name, namespace, labels, and annotations, as well as the allocated resources and limits for the container.</p>

<p>Exposing this information within containers has several practical applications. For instance, it enables applications to adjust their behavior according to the available resources or the environment they are running in. Additionally, it can facilitate the development of monitoring and logging tools that can access and report the pod’s metadata, making it easier to track and manage applications deployed in a Kubernetes cluster.</p>

<p>Furthermore, it helps create self-aware applications that can dynamically adapt to their environment, improving overall system resilience and responsiveness. This feature can be particularly useful when dealing with auto-scaling, rolling updates, and other scenarios where applications must gracefully handle changes in their environment.</p>

<p>The Kubernetes Downward API can be an invaluable tool for developers and operators working with containerized applications in a cluster environment. By providing easy access to critical pod metadata and container resources, it allows for more robust, flexible, and adaptive applications that can better handle the ever-changing landscape of modern distributed systems.</p>]]></content><author><name>Johannes Hölzel</name></author><category term="kubernetes" /><category term="kubernetes" /><category term="kubernetes-pod" /><category term="kubernetes-downward-api" /><summary type="html"><![CDATA[In the realm of Kubernetes, it is sometimes important to expose certain Pod and container fields to containers running within the Pod. These approaches, collectively known as the downward API, enable developers to harness the power of Pod and container fields within their containers.]]></summary></entry></feed>