<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.5">Jekyll</generator><link href="http://localhost:4000/feed.xml" rel="self" type="application/atom+xml" /><link href="http://localhost:4000/" rel="alternate" type="text/html" /><updated>2026-08-05T00:12:32+05:30</updated><id>http://localhost:4000/feed.xml</id><title type="html">Sushil Verma</title><subtitle>Online Tutorials - The Best Tutorials on latest technologies including GoLang, Python, PHP, Java, Linux, Cloud computing, DevOps</subtitle><author><name>Sushil Verma</name><email>contact@timesofcloud.com</email></author><entry><title type="html">AWS EKS Karpenter: Intelligent Kubernetes Node Autoscaling and Provisioning</title><link href="http://localhost:4000/aws-eks-karpenter-intelligent-node-autoscaling/" rel="alternate" type="text/html" title="AWS EKS Karpenter: Intelligent Kubernetes Node Autoscaling and Provisioning" /><published>2026-06-29T10:00:00+05:30</published><updated>2026-06-29T10:00:00+05:30</updated><id>http://localhost:4000/aws-eks-karpenter-intelligent-node-autoscaling</id><content type="html" xml:base="http://localhost:4000/aws-eks-karpenter-intelligent-node-autoscaling/"><![CDATA[<img src="/assets/images/blogs/aws-eks-karpenter-intelligent-node-autoscaling.svg" alt="AWS EKS Karpenter: Intelligent Kubernetes node autoscaling with dynamic provisioning and cost optimization" width="1050" height="520">

It's 3 AM. Your batch processing job just kicked off—5,000 pods need to run in the next hour. Cluster Autoscaler stares at pending pods, takes 4 minutes to realize it needs more nodes, spins up a node group with fixed instance types, waits 6 minutes for nodes to join the cluster, and oh look, half your pods are still pending because the instance type has GPUs but your workload needed high memory. By the time everything's running, you've blown your SLA and wasted $400 on oversized instances.

Enter Karpenter. It would've launched the exact instance types your pods needed—mix of memory-optimized and compute-optimized—in 40 seconds flat.

## What Is Karpenter?

**Karpenter** is an open-source Kubernetes node autoscaler built by AWS that directly provisions EC2 instances based on pending pod requirements. Unlike Cluster Autoscaler (which operates on fixed node groups), Karpenter evaluates each pod's resource requests and constraints, then launches the optimal instance type from a flexible pool.

**Why Karpenter Beats Cluster Autoscaler:**

- **Lightning-fast provisioning:** Nodes join the cluster in 30-45 seconds (vs 5-8 minutes with Cluster Autoscaler).
- **Instance flexibility:** Picks from hundreds of instance types instead of being locked to node group configs.
- **Bin-packing efficiency:** Provisions the smallest instance that fits your pods, reducing waste.
- **Cost optimization:** Can launch spot instances, graviton instances, or mix-and-match based on pod constraints.
- **Simpler configuration:** No need to pre-create node groups for every workload type.
- **Multi-AZ by default:** Automatically distributes nodes across availability zones.

## How Karpenter Works

```mermaid
graph TB
    A[Pending Pods] -->|Scheduler can't place| B[Karpenter Controller]
    B -->|Analyzes pod requirements| C[Instance Selection]
    C -->|Evaluates constraints| D[Launch EC2 Instance]
    D -->|Bootstraps & joins cluster| E[Kubelet Ready]
    E -->|Pods scheduled| F[Running Workload]
    B -->|Monitors utilization| G[Consolidation]
    G -->|Replaces underutilized nodes| H[Cost Savings]
```

**Key workflow:**

1. Pods enter pending state (no capacity)
2. Karpenter sees unschedulable pods within 10 seconds
3. Evaluates pod resource requests, node selectors, affinities, taints/tolerations
4. Picks optimal instance type(s) from provisioner configuration
5. Launches EC2 instance(s) directly via AWS APIs
6. Instance bootstraps, joins cluster in ~30-45 seconds
7. Scheduler places pods on new nodes
8. Karpenter continuously monitors for underutilized nodes and consolidates

## Installing Karpenter on EKS

### Prerequisites

```bash
# Set environment variables
export CLUSTER_NAME="my-eks-cluster"
export AWS_REGION="us-west-2"
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export KARPENTER_VERSION="v0.32.0"

# Verify EKS cluster exists
aws eks describe-cluster --name $CLUSTER_NAME --region $AWS_REGION
```

### Step 1: Create Karpenter IAM Resources

```bash
# Create IAM role for Karpenter controller
KARPENTER_ROLE="KarpenterControllerRole-${CLUSTER_NAME}"

cat > karpenter-trust-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/oidc.eks.${AWS_REGION}.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.${AWS_REGION}.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:karpenter:karpenter"
        }
      }
    }
  ]
}
EOF

# Create the role
aws iam create-role --role-name $KARPENTER_ROLE \
  --assume-role-policy-document file://karpenter-trust-policy.json

# Attach required policies
aws iam attach-role-policy --role-name $KARPENTER_ROLE \
  --policy-arn arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy

aws iam attach-role-policy --role-name $KARPENTER_ROLE \
  --policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly

aws iam attach-role-policy --role-name $KARPENTER_ROLE \
  --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
```

### Step 2: Install Karpenter via Helm

```bash
# Add Karpenter Helm repository
helm repo add karpenter https://charts.karpenter.sh
helm repo update

# Install Karpenter
helm upgrade --install karpenter karpenter/karpenter \
  --namespace karpenter \
  --create-namespace \
  --version ${KARPENTER_VERSION} \
  --set settings.aws.clusterName=${CLUSTER_NAME} \
  --set settings.aws.defaultInstanceProfile=KarpenterNodeInstanceProfile \
  --set settings.aws.interruptionQueueName=${CLUSTER_NAME} \
  --set controller.resources.requests.cpu=1 \
  --set controller.resources.requests.memory=1Gi \
  --set controller.resources.limits.cpu=1 \
  --set controller.resources.limits.memory=1Gi \
  --wait

# Verify installation
kubectl get pods -n karpenter
# Output: karpenter-controller-... Running
```

### Step 3: Create EC2NodeClass

```yaml
# ec2-node-class.yaml
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
  name: default
spec:
  # AMI Selection
  amiFamily: AL2
  
  # IAM Role for nodes
  role: "KarpenterNodeRole-${CLUSTER_NAME}"
  
  # Subnet discovery via tags
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: ${CLUSTER_NAME}
  
  # Security group discovery
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: ${CLUSTER_NAME}
  
  # User data for node bootstrap
  userData: |
    #!/bin/bash
    /etc/eks/bootstrap.sh ${CLUSTER_NAME}
  
  # Block device mappings
  blockDeviceMappings:
    - deviceName: /dev/xvda
      ebs:
        volumeSize: 100Gi
        volumeType: gp3
        encrypted: true
        deleteOnTermination: true
  
  # Metadata options
  metadataOptions:
    httpEndpoint: enabled
    httpProtocolIPv6: disabled
    httpPutResponseHopLimit: 2
    httpTokens: required
```

### Step 4: Create NodePool

```yaml
# node-pool.yaml
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: default
spec:
  # Template for nodes
  template:
    spec:
      nodeClassRef:
        name: default
      
      requirements:
        # Instance categories
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["on-demand", "spot"]
        
        # Instance families
        - key: karpenter.k8s.aws/instance-family
          operator: In
          values: ["c5", "c5a", "c6i", "m5", "m5a", "m6i", "r5", "r5a", "r6i"]
        
        # CPU architecture
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64", "arm64"]
        
        # Instance size constraints
        - key: karpenter.k8s.aws/instance-size
          operator: NotIn
          values: ["nano", "micro", "small"]
      
      # Taints (optional)
      taints:
        - key: workload
          value: batch
          effect: NoSchedule
  
  # Limits
  limits:
    cpu: 1000
    memory: 1000Gi
  
  # Disruption budget
  disruption:
    consolidationPolicy: WhenUnderutilized
    expireAfter: 720h  # 30 days
  
  # Weight for multi-pool scenarios
  weight: 10
```

Apply the configuration:

```bash
kubectl apply -f ec2-node-class.yaml
kubectl apply -f node-pool.yaml

# Verify NodePool
kubectl get nodepool
# Output: NAME      READY   AGE
#         default   True    5s
```

## Real-World Example: Mixed Workload Cluster

```yaml
# production-nodepools.yaml
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: general-purpose
spec:
  template:
    spec:
      nodeClassRef:
        name: default
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
        - key: karpenter.k8s.aws/instance-family
          operator: In
          values: ["c6i", "c6a", "m6i", "m6a"]
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64"]
  limits:
    cpu: 500
  disruption:
    consolidationPolicy: WhenUnderutilized
  weight: 100

---
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: memory-optimized
spec:
  template:
    spec:
      nodeClassRef:
        name: default
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["on-demand"]  # Stateful workloads, no spot
        - key: karpenter.k8s.aws/instance-family
          operator: In
          values: ["r6i", "r6a", "r5"]
        - key: node.kubernetes.io/instance-type
          operator: In
          values: ["r6i.xlarge", "r6i.2xlarge", "r6i.4xlarge"]
      taints:
        - key: workload-type
          value: memory-intensive
          effect: NoSchedule
  limits:
    cpu: 200
  disruption:
    consolidationPolicy: WhenEmpty
    consolidateAfter: 5m

---
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: graviton-spot
spec:
  template:
    spec:
      nodeClassRef:
        name: default
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot"]
        - key: kubernetes.io/arch
          operator: In
          values: ["arm64"]  # Graviton instances
        - key: karpenter.k8s.aws/instance-family
          operator: In
          values: ["c7g", "m7g", "r7g"]
      taints:
        - key: arch
          value: arm64
          effect: NoSchedule
  limits:
    cpu: 300
  weight: 50
```

Deploy workloads targeting specific pools:

```yaml
# deployment-memory-intensive.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-cluster
spec:
  replicas: 3
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      tolerations:
        - key: workload-type
          value: memory-intensive
          effect: NoSchedule
      nodeSelector:
        karpenter.sh/capacity-type: on-demand
      containers:
        - name: redis
          image: redis:7-alpine
          resources:
            requests:
              cpu: 2
              memory: 16Gi  # Karpenter will provision r6i.xlarge or larger
            limits:
              cpu: 2
              memory: 16Gi
```

Test autoscaling:

```bash
# Create test workload with 50 replicas
kubectl create deployment inflate --image=public.ecr.aws/eks-distro/kubernetes/pause:3.2 --replicas=50

# Watch Karpenter provision nodes
kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller

# Example log output:
# INFO  controller.provisioner  found provisionable pod(s)  {"commit": "abc123", "pods": 50}
# INFO  controller.provisioner  computed new node(s) to fit pod(s)  {"commit": "abc123", "newNodes": 3, "pods": 50}
# INFO  controller.provisioner  launched instance  {"commit": "abc123", "id": "i-0abc123", "type": "c6i.2xlarge"}

# Check nodes
kubectl get nodes -l karpenter.sh/provisioner-name=general-purpose

# Clean up
kubectl delete deployment inflate
```

## Monitoring Karpenter

```yaml
# servicemonitor.yaml for Prometheus
apiVersion: v1
kind: Service
metadata:
  name: karpenter-metrics
  namespace: karpenter
  labels:
    app: karpenter
spec:
  ports:
    - name: metrics
      port: 8080
      targetPort: metrics
  selector:
    app.kubernetes.io/name: karpenter

---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: karpenter
  namespace: karpenter
spec:
  selector:
    matchLabels:
      app: karpenter
  endpoints:
    - port: metrics
      interval: 30s
```

Key metrics to monitor:

```promql
# Pending pods that triggered provisioning
karpenter_pods_state{state="pending"}

# Number of nodes managed by Karpenter
karpenter_nodes_total

# Provisioning duration (time to launch node)
histogram_quantile(0.99, rate(karpenter_provisioner_scheduling_duration_seconds_bucket[5m]))

# Node utilization before consolidation
karpenter_nodes_utilization{resource="cpu"}
karpenter_nodes_utilization{resource="memory"}

# Spot interruptions
karpenter_interruption_actions_performed_total
```

## Best Practices

1. **Start with generous instance type lists** — Let Karpenter pick from broad families (c5, c6i, m5, m6i). Narrow down only if you hit compatibility issues. More options = better bin packing.

2. **Use spot instances for fault-tolerant workloads** — Set `karpenter.sh/capacity-type: spot` in pod node affinity. Karpenter handles interruptions gracefully. We run 70% of our batch jobs on spot with zero manual intervention.

3. **Set resource requests accurately** — Karpenter provisions based on `resources.requests`, not limits. If your pod requests 100m CPU but actually needs 2 CPU, Karpenter will provision a tiny instance and your pod will throttle.

4. **Enable consolidation for cost savings** — `consolidationPolicy: WhenUnderutilized` lets Karpenter replace 3 half-empty nodes with 1 full node. We've seen 25-35% cost reduction from consolidation alone.

5. **Use multiple NodePools for workload isolation** — Separate general-purpose, memory-intensive, and GPU workloads. This prevents resource contention and gives you better cost visibility per workload type.

6. **Tag subnets and security groups** — Karpenter discovers infrastructure via tags like `karpenter.sh/discovery: ${CLUSTER_NAME}`. Tag your private subnets and node security groups consistently across all AZs.

7. **Monitor provisioning latency** — Track `karpenter_provisioner_scheduling_duration_seconds`. If you're seeing >60 seconds, check for API rate limiting or EC2 capacity constraints in your region.

8. **Set reasonable limits per NodePool** — Prevent runaway scaling by setting CPU/memory limits. Start with 2-3x your expected peak and adjust based on usage patterns.

## Conclusion

Karpenter fundamentally changes how Kubernetes clusters scale on AWS. By provisioning nodes in under a minute with the exact instance type your pods need, it eliminates the guesswork and overprovisioning that plague traditional Cluster Autoscaler setups. The shift from managing node groups to defining flexible requirements feels like upgrading from manually partitioning hard drives to using LVM—you wonder how you ever tolerated the old way.

**Key Takeaways:**

- Karpenter provisions nodes 5-10x faster than Cluster Autoscaler (30-45 seconds vs 5-8 minutes)
- Supports hundreds of instance types with flexible selection based on pod requirements
- Automatic consolidation reduces costs by 25-35% by replacing underutilized nodes
- Spot instance support with built-in interruption handling for fault-tolerant workloads
- Multiple NodePools enable workload isolation and cost optimization per workload type

Replace Cluster Autoscaler with Karpenter on one non-production cluster this week. Watch it provision a mix of c6i.xlarge, m6i.2xlarge, and r6i.large instances for different pods while your old node groups sit there with fixed instance types. The $4,000/month savings on a medium-sized cluster pays for itself in two weeks.

## Frequently Asked Questions

**Q: Can I use Karpenter alongside Cluster Autoscaler?**

Yes, but not recommended long-term. You can run both simultaneously during migration—Cluster Autoscaler manages existing node groups while Karpenter provisions new nodes. Ensure pods targeting Karpenter nodes have node selectors or taints to prevent scheduler conflicts. Once you've validated Karpenter, remove Cluster Autoscaler entirely to avoid competing scaling decisions and wasted resources.

**Q: What happens when spot instances get interrupted?**

Karpenter receives spot interruption warnings (2 minutes before termination) and automatically cordons the node, drains pods gracefully, and provisions replacement capacity. Pods get rescheduled to other nodes or new nodes if needed. Enable Pod Disruption Budgets (PDBs) to ensure minimum availability during interruptions. We've run 60% spot for 18 months with zero user-facing incidents.

**Q: How does Karpenter handle GPU instances?**

Karpenter supports GPU instance selection via node requirements. Specify `nvidia.com/gpu` in pod resource requests, and include `p3`, `p4`, `g4dn`, or `g5` instance families in your NodePool. Karpenter automatically picks the right GPU instance based on GPU count and memory. For multi-GPU pods, it provisions instances with the exact GPU count needed (e.g., p3.8xlarge for 4 GPUs).

**Q: Can Karpenter provision nodes in multiple availability zones?**

Yes, by default. Tag subnets in all AZs with `karpenter.sh/discovery: ${CLUSTER_NAME}`, and Karpenter distributes nodes across zones automatically for high availability. You can also use `topology.kubernetes.io/zone` in pod affinity rules to influence zone placement. Karpenter respects `topologySpreadConstraints` to balance pods evenly across zones.

**Q: How do I migrate from Cluster Autoscaler without downtime?**

Gradual migration: (1) Install Karpenter alongside Cluster Autoscaler, (2) Create Karpenter NodePools with taints, (3) Deploy new workloads with matching tolerations targeting Karpenter nodes, (4) Monitor for 1-2 weeks, (5) Drain existing node groups one at a time, letting Karpenter provision replacements, (6) Uninstall Cluster Autoscaler once all workloads migrated. Use `karpenter.sh/do-not-disrupt: "true"` annotation on pods during migration to prevent premature consolidation.]]></content><author><name>Sushil Verma</name></author><category term="AWS" /><category term="Kubernetes" /><category term="kubernetes" /><category term="eks" /><category term="karpenter" /><category term="autoscaling" /><category term="aws" /><category term="node-provisioning" /><summary type="html"><![CDATA[Replace Cluster Autoscaler with Karpenter for faster, smarter EKS node provisioning that launches right-sized instances in under 30 seconds.]]></summary></entry><entry><title type="html">Terraform Provider Development: Building Custom Providers with the Plugin Framework</title><link href="http://localhost:4000/terraform-provider-development-custom-providers-plugin-framework/" rel="alternate" type="text/html" title="Terraform Provider Development: Building Custom Providers with the Plugin Framework" /><published>2026-06-25T10:00:00+05:30</published><updated>2026-06-25T10:00:00+05:30</updated><id>http://localhost:4000/terraform-provider-development-custom-providers-plugin-framework</id><content type="html" xml:base="http://localhost:4000/terraform-provider-development-custom-providers-plugin-framework/"><![CDATA[<img src="/assets/images/blogs/terraform-provider-development-custom-plugin-framework.svg" alt="Terraform Provider Development: Building custom Terraform providers with Plugin Framework, Go code, and infrastructure API integration" width="1050" height="520">

Picture this: Your company has an internal platform API that provisions dev environments, manages feature flags, and configures service mesh policies. Your infrastructure team manually calls these APIs through curl scripts scattered across Slack threads and runbooks. Someone suggests "just Terraform it," but there's no official provider. You're stuck clicking through UIs, running bash scripts, and praying nobody typos a production environment name.

This is exactly when you need a custom Terraform provider. And thanks to HashiCorp's Plugin Framework (introduced to replace the older SDK), building one is less painful than you'd think.

## What Is a Terraform Provider?

A **Terraform provider** is a plugin that translates Terraform's declarative configuration into API calls against a real system—cloud platforms (AWS, Azure), SaaS tools (Datadog, PagerDuty), or your own internal APIs.

**Why Build Custom Providers:**

- **Manage internal platforms as code:** Treat your homegrown APIs with the same IaC discipline as AWS resources.
- **Eliminate manual toil:** Replace curl scripts and UI clicking with `terraform apply`.
- **Enable self-service infrastructure:** Let developers provision their own environments through Git pull requests.
- **Standardize configurations:** Prevent drift by enforcing configuration through code review.
- **Integrate with existing workflows:** Use Terraform's state management, drift detection, and plan/apply flow for custom resources.
- **Type safety and validation:** Provider schemas catch errors at plan time, not runtime.

## Provider Architecture Overview

```mermaid
graph LR
    A[Terraform CLI] -->|gRPC| B[Provider Plugin]
    B -->|CRUD Operations| C[Target API]
    B -->|Schema & Validation| D[Resource Definitions]
    D -->|State Management| E[Terraform State]
```

**Key components:**

- **Provider:** Configures authentication and connection to the target API
- **Resources:** Entities that can be created, read, updated, deleted (CRUD)
- **Data Sources:** Read-only queries that fetch existing data
- **Schemas:** Type definitions and validation rules for each resource

## Setting Up Your Development Environment

### Prerequisites

```bash
# Install Go 1.21+
brew install go

# Install Terraform 1.5+
brew install terraform

# Verify versions
go version  # go version go1.21.0 darwin/arm64
terraform version  # Terraform v1.5.7

# Create project structure
mkdir terraform-provider-myapi
cd terraform-provider-myapi

# Initialize Go module
go mod init github.com/yourorg/terraform-provider-myapi
```

### Install HashiCorp's Plugin Framework

```go
// go.mod
module github.com/yourorg/terraform-provider-myapi

go 1.21

require (
    github.com/hashicorp/terraform-plugin-framework v1.4.2
    github.com/hashicorp/terraform-plugin-go v0.19.0
    github.com/hashicorp/terraform-plugin-log v0.9.0
)
```

Run `go mod tidy` to download dependencies.

## Building Your First Provider

### Step 1: Define the Provider Configuration

```go
// internal/provider/provider.go
package provider

import (
    "context"
    "os"

    "github.com/hashicorp/terraform-plugin-framework/datasource"
    "github.com/hashicorp/terraform-plugin-framework/provider"
    "github.com/hashicorp/terraform-plugin-framework/provider/schema"
    "github.com/hashicorp/terraform-plugin-framework/resource"
    "github.com/hashicorp/terraform-plugin-framework/types"
)

// Ensure the implementation satisfies the provider.Provider interface
var _ provider.Provider = &MyAPIProvider{}

type MyAPIProvider struct {
    version string
}

// Provider configuration model
type MyAPIProviderModel struct {
    Endpoint types.String `tfsdk:"endpoint"`
    APIKey   types.String `tfsdk:"api_key"`
}

func New(version string) func() provider.Provider {
    return func() provider.Provider {
        return &MyAPIProvider{
            version: version,
        }
    }
}

func (p *MyAPIProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
    resp.TypeName = "myapi"
    resp.Version = p.version
}

func (p *MyAPIProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
    resp.Schema = schema.Schema{
        Attributes: map[string]schema.Attribute{
            "endpoint": schema.StringAttribute{
                MarkdownDescription: "MyAPI endpoint URL",
                Optional:            true,
            },
            "api_key": schema.StringAttribute{
                MarkdownDescription: "API key for authentication",
                Optional:            true,
                Sensitive:           true,
            },
        },
    }
}

func (p *MyAPIProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
    var config MyAPIProviderModel

    resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)

    if resp.Diagnostics.HasError() {
        return
    }

    // Allow environment variable overrides
    endpoint := os.Getenv("MYAPI_ENDPOINT")
    apiKey := os.Getenv("MYAPI_API_KEY")

    if !config.Endpoint.IsNull() {
        endpoint = config.Endpoint.ValueString()
    }

    if !config.APIKey.IsNull() {
        apiKey = config.APIKey.ValueString()
    }

    if endpoint == "" {
        resp.Diagnostics.AddError(
            "Missing API Endpoint",
            "The provider cannot create the API client without an endpoint.",
        )
    }

    if apiKey == "" {
        resp.Diagnostics.AddError(
            "Missing API Key",
            "The provider cannot create the API client without an API key.",
        )
    }

    if resp.Diagnostics.HasError() {
        return
    }

    // Create API client and store in provider data
    client := &APIClient{
        Endpoint: endpoint,
        APIKey:   apiKey,
    }

    resp.DataSourceData = client
    resp.ResourceData = client
}

func (p *MyAPIProvider) Resources(ctx context.Context) []func() resource.Resource {
    return []func() resource.Resource{
        NewEnvironmentResource,
    }
}

func (p *MyAPIProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
    return []func() datasource.DataSource{}
}
```

### Step 2: Create a Resource

```go
// internal/provider/environment_resource.go
package provider

import (
    "context"
    "fmt"

    "github.com/hashicorp/terraform-plugin-framework/resource"
    "github.com/hashicorp/terraform-plugin-framework/resource/schema"
    "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
    "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
    "github.com/hashicorp/terraform-plugin-framework/types"
)

var _ resource.Resource = &EnvironmentResource{}

func NewEnvironmentResource() resource.Resource {
    return &EnvironmentResource{}
}

type EnvironmentResource struct {
    client *APIClient
}

type EnvironmentResourceModel struct {
    ID       types.String `tfsdk:"id"`
    Name     types.String `tfsdk:"name"`
    Type     types.String `tfsdk:"type"`
    Region   types.String `tfsdk:"region"`
    Tags     types.Map    `tfsdk:"tags"`
}

func (r *EnvironmentResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
    resp.TypeName = req.ProviderTypeName + "_environment"
}

func (r *EnvironmentResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
    resp.Schema = schema.Schema{
        MarkdownDescription: "Manages development environment resources",
        Attributes: map[string]schema.Attribute{
            "id": schema.StringAttribute{
                Computed:            true,
                MarkdownDescription: "Environment identifier",
                PlanModifiers: []planmodifier.String{
                    stringplanmodifier.UseStateForUnknown(),
                },
            },
            "name": schema.StringAttribute{
                MarkdownDescription: "Environment name",
                Required:            true,
            },
            "type": schema.StringAttribute{
                MarkdownDescription: "Environment type (dev, staging, prod)",
                Required:            true,
            },
            "region": schema.StringAttribute{
                MarkdownDescription: "Deployment region",
                Optional:            true,
                Computed:            true,
            },
            "tags": schema.MapAttribute{
                MarkdownDescription: "Key-value tags",
                Optional:            true,
                ElementType:         types.StringType,
            },
        },
    }
}

func (r *EnvironmentResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
    if req.ProviderData == nil {
        return
    }

    client, ok := req.ProviderData.(*APIClient)
    if !ok {
        resp.Diagnostics.AddError(
            "Unexpected Resource Configure Type",
            fmt.Sprintf("Expected *APIClient, got: %T", req.ProviderData),
        )
        return
    }

    r.client = client
}

func (r *EnvironmentResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
    var data EnvironmentResourceModel

    resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
    if resp.Diagnostics.HasError() {
        return
    }

    // Call your API to create the environment
    env, err := r.client.CreateEnvironment(ctx, EnvironmentRequest{
        Name:   data.Name.ValueString(),
        Type:   data.Type.ValueString(),
        Region: data.Region.ValueString(),
    })

    if err != nil {
        resp.Diagnostics.AddError("API Error", fmt.Sprintf("Failed to create environment: %s", err))
        return
    }

    // Save to state
    data.ID = types.StringValue(env.ID)
    data.Region = types.StringValue(env.Region)

    resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *EnvironmentResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
    var data EnvironmentResourceModel

    resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
    if resp.Diagnostics.HasError() {
        return
    }

    env, err := r.client.GetEnvironment(ctx, data.ID.ValueString())
    if err != nil {
        resp.Diagnostics.AddError("API Error", fmt.Sprintf("Failed to read environment: %s", err))
        return
    }

    data.Name = types.StringValue(env.Name)
    data.Type = types.StringValue(env.Type)
    data.Region = types.StringValue(env.Region)

    resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *EnvironmentResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
    var data EnvironmentResourceModel

    resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
    if resp.Diagnostics.HasError() {
        return
    }

    _, err := r.client.UpdateEnvironment(ctx, data.ID.ValueString(), EnvironmentRequest{
        Name:   data.Name.ValueString(),
        Type:   data.Type.ValueString(),
        Region: data.Region.ValueString(),
    })

    if err != nil {
        resp.Diagnostics.AddError("API Error", fmt.Sprintf("Failed to update environment: %s", err))
        return
    }

    resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *EnvironmentResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
    var data EnvironmentResourceModel

    resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
    if resp.Diagnostics.HasError() {
        return
    }

    err := r.client.DeleteEnvironment(ctx, data.ID.ValueString())
    if err != nil {
        resp.Diagnostics.AddError("API Error", fmt.Sprintf("Failed to delete environment: %s", err))
        return
    }
}
```

### Step 3: Implement the API Client

```go
// internal/provider/client.go
package provider

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

type APIClient struct {
    Endpoint   string
    APIKey     string
    HTTPClient *http.Client
}

type EnvironmentRequest struct {
    Name   string `json:"name"`
    Type   string `json:"type"`
    Region string `json:"region"`
}

type Environment struct {
    ID     string `json:"id"`
    Name   string `json:"name"`
    Type   string `json:"type"`
    Region string `json:"region"`
}

func (c *APIClient) CreateEnvironment(ctx context.Context, req EnvironmentRequest) (*Environment, error) {
    body, _ := json.Marshal(req)
    
    httpReq, err := http.NewRequestWithContext(ctx, "POST", c.Endpoint+"/environments", bytes.NewBuffer(body))
    if err != nil {
        return nil, err
    }

    httpReq.Header.Set("Authorization", "Bearer "+c.APIKey)
    httpReq.Header.Set("Content-Type", "application/json")

    resp, err := c.HTTPClient.Do(httpReq)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusCreated {
        bodyBytes, _ := io.ReadAll(resp.Body)
        return nil, fmt.Errorf("API returned %d: %s", resp.StatusCode, string(bodyBytes))
    }

    var env Environment
    if err := json.NewDecoder(resp.Body).Decode(&env); err != nil {
        return nil, err
    }

    return &env, nil
}

func (c *APIClient) GetEnvironment(ctx context.Context, id string) (*Environment, error) {
    httpReq, err := http.NewRequestWithContext(ctx, "GET", c.Endpoint+"/environments/"+id, nil)
    if err != nil {
        return nil, err
    }

    httpReq.Header.Set("Authorization", "Bearer "+c.APIKey)

    resp, err := c.HTTPClient.Do(httpReq)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    if resp.StatusCode == http.StatusNotFound {
        return nil, fmt.Errorf("environment %s not found", id)
    }

    var env Environment
    if err := json.NewDecoder(resp.Body).Decode(&env); err != nil {
        return nil, err
    }

    return &env, nil
}

func (c *APIClient) UpdateEnvironment(ctx context.Context, id string, req EnvironmentRequest) (*Environment, error) {
    body, _ := json.Marshal(req)
    
    httpReq, err := http.NewRequestWithContext(ctx, "PUT", c.Endpoint+"/environments/"+id, bytes.NewBuffer(body))
    if err != nil {
        return nil, err
    }

    httpReq.Header.Set("Authorization", "Bearer "+c.APIKey)
    httpReq.Header.Set("Content-Type", "application/json")

    resp, err := c.HTTPClient.Do(httpReq)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var env Environment
    if err := json.NewDecoder(resp.Body).Decode(&env); err != nil {
        return nil, err
    }

    return &env, nil
}

func (c *APIClient) DeleteEnvironment(ctx context.Context, id string) error {
    httpReq, err := http.NewRequestWithContext(ctx, "DELETE", c.Endpoint+"/environments/"+id, nil)
    if err != nil {
        return err
    }

    httpReq.Header.Set("Authorization", "Bearer "+c.APIKey)

    resp, err := c.HTTPClient.Do(httpReq)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK {
        return fmt.Errorf("failed to delete environment, status: %d", resp.StatusCode)
    }

    return nil
}
```

### Step 4: Create the Main Entry Point

```go
// main.go
package main

import (
    "context"
    "flag"
    "log"

    "github.com/hashicorp/terraform-plugin-framework/providerserver"
    "github.com/yourorg/terraform-provider-myapi/internal/provider"
)

var version string = "dev"

func main() {
    var debug bool

    flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers")
    flag.Parse()

    opts := providerserver.ServeOpts{
        Address: "registry.terraform.io/yourorg/myapi",
        Debug:   debug,
    }

    err := providerserver.Serve(context.Background(), provider.New(version), opts)
    if err != nil {
        log.Fatal(err.Error())
    }
}
```

## Real-World Example: Complete Configuration

```hcl
# main.tf
terraform {
  required_providers {
    myapi = {
      source  = "yourorg/myapi"
      version = "~> 1.0"
    }
  }
}

provider "myapi" {
  endpoint = "https://api.company.internal"
  api_key  = var.api_key  # Or use MYAPI_API_KEY env var
}

resource "myapi_environment" "dev" {
  name   = "feature-123-dev"
  type   = "development"
  region = "us-west-2"
  
  tags = {
    team    = "platform"
    project = "feature-123"
    cost_center = "engineering"
  }
}

resource "myapi_environment" "staging" {
  name   = "feature-123-staging"
  type   = "staging"
  region = "us-west-2"
  
  tags = {
    team    = "platform"
    project = "feature-123"
  }
}

output "dev_environment_id" {
  value = myapi_environment.dev.id
}
```

Usage:

```bash
# Initialize and install the provider
terraform init

# Preview changes
terraform plan

# Apply configuration
terraform apply

# Output: dev_environment_id = "env-abc123"

# Clean up
terraform destroy
```

## Best Practices

1. **Use context-aware HTTP clients** — Respect Terraform's context cancellation for long-running API calls. Always use `http.NewRequestWithContext(ctx, ...)`.

2. **Implement proper error handling** — Return detailed error messages with diagnostics. Users should understand WHY the API call failed, not just that it failed.

3. **Add comprehensive schema validation** — Use `Validators` to catch invalid configurations at plan time: `stringvalidator.OneOf("dev", "staging", "prod")`.

4. **Handle API idempotency** — Ensure Create operations handle "already exists" responses gracefully. Update operations should be idempotent.

5. **Implement retries with exponential backoff** — APIs fail. Wrap HTTP calls with retry logic for transient errors (429, 503).

6. **Use plan modifiers wisely** — For computed attributes that shouldn't force replacement, use `UseStateForUnknown()`. For attributes that require resource recreation, use `RequiresReplace()`.

7. **Write acceptance tests** — Use Terraform's testing framework to validate CRUD operations against a test API or mock server.

8. **Version your provider** — Follow semantic versioning. Breaking schema changes require major version bumps.

## Conclusion

Building custom Terraform providers unlocks IaC for your internal platforms, eliminating manual API calls and bringing consistency to infrastructure provisioning. The Plugin Framework's type-safe approach catches errors at compile time, and its declarative nature makes provider development far more approachable than the old SDK.

**Key Takeaways:**

- Custom providers extend Terraform to manage any API, internal or external
- HashiCorp's Plugin Framework uses Go with strong typing and schema validation
- Provider implements Configure, Resources, and DataSources methods
- Resources need Create, Read, Update, Delete (CRUD) operations
- Use context-aware HTTP clients and proper error handling for production readiness

Start with a single resource for your most-used internal API. As your team adopts it, expand to cover more resources. The infrastructure-as-code mindset spreads faster when you meet developers where they are—managing everything through `terraform apply`.

## Frequently Asked Questions

**Q: Do I need to publish my provider to the Terraform Registry?**

No. For internal use, you can install providers locally or host them in a private registry. Place the compiled binary in `~/.terraform.d/plugins/registry.terraform.io/yourorg/myapi/1.0.0/<OS_ARCH>/` and Terraform will find it during `terraform init`. For team distribution, use Terraform Cloud's private registry or a self-hosted artifact repository.

**Q: How do I handle authentication with multiple methods (API keys, OAuth, service accounts)?**

Add multiple optional authentication attributes to your provider schema and implement fallback logic in the `Configure` method. Example: check for `api_key` first, then `oauth_token`, then `service_account_path`. Document the precedence order clearly. You can also support multiple provider aliases for different authentication methods simultaneously.

**Q: What's the difference between the Plugin Framework and the older SDK?**

The Plugin Framework (v1.0+, released 2022) uses protocol version 6 with better type safety, clearer error messages, and first-class support for nested attributes and plan modifiers. The older SDK uses protocol version 5 and relies heavily on `map[string]interface{}` which loses compile-time type checking. New providers should use the Plugin Framework; the SDK is in maintenance mode.

**Q: How do I test my provider without calling the real API?**

Use Go's `httptest` package to create a mock HTTP server that returns canned responses. In your tests, configure the provider to point at the mock server endpoint instead of production. For more comprehensive testing, use Terraform's acceptance testing framework with the `TF_ACC=1` environment variable to run full CRUD lifecycle tests against a sandbox API environment.

**Q: Can I use a custom provider with Terraform Cloud or Spacelift?**

Yes. Terraform Cloud supports custom providers if you publish them to a private registry (either Terraform Cloud's registry or your own). Upload the provider binary and configure your workspace to use it. For self-hosted Terraform Enterprise, you have even more flexibility with airgapped installations. Spacelift supports custom providers through its worker pool configuration—just ensure the provider binary is available in the worker's plugin directory.]]></content><author><name>Sushil Verma</name></author><category term="DevOps" /><category term="Terraform" /><category term="terraform" /><category term="providers" /><category term="plugin-framework" /><category term="golang" /><category term="iac" /><category term="custom-provider" /><summary type="html"><![CDATA[Build custom Terraform providers to manage internal APIs and proprietary infrastructure using HashiCorp's Plugin Framework with Go.]]></summary></entry><entry><title type="html">EKS IRSA and Pod Identity: Fine-Grained AWS Permissions Without Static Keys</title><link href="http://localhost:4000/eks-irsa-pod-identity-aws-permissions/" rel="alternate" type="text/html" title="EKS IRSA and Pod Identity: Fine-Grained AWS Permissions Without Static Keys" /><published>2026-06-24T09:30:00+05:30</published><updated>2026-06-24T09:30:00+05:30</updated><id>http://localhost:4000/eks-irsa-pod-identity-aws-permissions</id><content type="html" xml:base="http://localhost:4000/eks-irsa-pod-identity-aws-permissions/"><![CDATA[<img src="/assets/images/blogs/eks-irsa-pod-identity-aws-permissions.svg" alt="EKS IRSA and Pod Identity Architecture: How Kubernetes pods get AWS permissions through OIDC, IAM roles, and STS temporary credentials without static keys" width="1050" height="520">

Picture this: it's 3 AM, your security team just flagged an exposed AWS access key buried in a ConfigMap that somehow made it to GitHub, and your S3 buckets are being scraped by someone in a country you've never deployed to. Static credentials in containers are a disaster waiting to happen. There's a better way.

## What Are IRSA and Pod Identity?

**IAM Roles for Service Accounts (IRSA)** and **EKS Pod Identity** are two AWS mechanisms that let Kubernetes pods assume IAM roles without embedding static credentials. Think of them like valet keys for your car — they work temporarily, have limited permissions, and automatically expire after use.

Here's how they differ from the old approach:

| Approach | How Credentials Work | Security Risk |
|----------|---------------------|---------------|
| **Static Keys (Bad)** | AWS_ACCESS_KEY_ID in env vars or secrets | Keys never expire, can leak via logs/containers |
| **Node IAM Role (Better)** | All pods on a node share the same role | Too permissive — one compromised pod = all permissions |
| **IRSA (Best)** | Each pod gets its own temporary credentials via OIDC | Scoped per pod, auto-rotated every hour |
| **Pod Identity (Newest)** | Simplified IRSA with fewer moving parts | Same security, easier setup (EKS 1.24+) |

**Key Benefits:**
- **No credential rotation needed:** Temporary credentials expire automatically every 60 minutes and refresh transparently.
- **Least privilege by design:** Each pod gets only the IAM permissions it needs, not the entire node role.
- **Audit trail:** Every AWS API call is tagged with the pod's service account, making CloudTrail logs useful.
- **Zero config in app code:** Your application uses the AWS SDK normally — no special credential handling required.
- **Works with third-party tools:** Fluent Bit, external-dns, ALB Ingress Controller — anything that calls AWS APIs.

> **What Most Tutorials Get Wrong:** Many guides treat IRSA and Pod Identity as interchangeable. They're not. IRSA has been around since 2019 and requires an OIDC provider setup. Pod Identity (GA in late 2023) is AWS's simpler alternative that skips OIDC entirely. If you're starting fresh on EKS 1.24+, use Pod Identity. If you already have IRSA working, don't rush to migrate — it's not deprecated.

## How IRSA Works Under the Hood

IRSA uses **OpenID Connect (OIDC)** to bridge Kubernetes identities to AWS IAM. Here's the flow:

1. EKS creates an OIDC provider in your AWS account tied to your cluster.
2. You create an IAM role and configure it to trust your cluster's OIDC provider.
3. You annotate a Kubernetes ServiceAccount with the IAM role ARN.
4. When a pod uses that ServiceAccount, EKS injects a web identity token into the pod.
5. The AWS SDK exchanges that token for temporary IAM credentials via STS AssumeRoleWithWebIdentity.

The pod never sees the credentials directly — they're fetched on-demand and cached by the SDK.

### Setting Up IRSA (EKS 1.23–1.30)

**Step 1: Create the OIDC Provider (one-time per cluster)**

```bash
# Get your cluster's OIDC issuer URL
OIDC_URL=$(aws eks describe-cluster \
  --name my-cluster \
  --region us-west-2 \
  --query "cluster.identity.oidc.issuer" \
  --output text)

# Extract the ID (last part after https://oidc.eks...)
OIDC_ID=$(echo $OIDC_URL | awk -F'/' '{print $NF}')

# Create OIDC provider if it doesn't exist
aws iam create-open-id-connect-provider \
  --url $OIDC_URL \
  --client-id-list sts.amazonaws.com \
  --thumbprint-list "9e99a48a9960b14926bb7f3b02e22da2b0ab7280"
```

That thumbprint is static for all EKS clusters — it's the AWS root CA thumbprint.

**Step 2: Create an IAM Role with Trust Policy**

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/${OIDC_ID}"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.us-west-2.amazonaws.com/id/${OIDC_ID}:sub": "system:serviceaccount:my-namespace:my-service-account",
          "oidc.eks.us-west-2.amazonaws.com/id/${OIDC_ID}:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}
```

The `Condition` block is critical — it restricts the role to **one specific ServiceAccount in one specific namespace**. Change the namespace or ServiceAccount name, and the trust breaks.

**Step 3: Attach Permissions to the Role**

```bash
# Inline policy example (S3 read-only for a specific bucket)
aws iam put-role-policy \
  --role-name my-pod-role \
  --policy-name s3-read-access \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::my-data-bucket",
        "arn:aws:s3:::my-data-bucket/*"
      ]
    }]
  }'
```

**Step 4: Annotate the Kubernetes ServiceAccount**

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-service-account
  namespace: my-namespace
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-pod-role
```

Apply this with `kubectl apply -f serviceaccount.yaml`.

**Step 5: Use the ServiceAccount in Your Pod**

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-app
  namespace: my-namespace
spec:
  serviceAccountName: my-service-account  # <-- This line is key
  containers:
  - name: app
    image: my-app:latest
    env:
    - name: AWS_REGION
      value: "us-west-2"
```

That's it. Your pod can now call AWS APIs with the permissions defined in `my-pod-role`.

## How Pod Identity Works (Simpler Alternative)

Pod Identity skips the OIDC provider entirely. AWS manages the identity plumbing behind the scenes using a new component called the **EKS Pod Identity Agent** (runs as a DaemonSet on every node).

### Setting Up Pod Identity (EKS 1.24+)

**Step 1: Enable Pod Identity on Your Cluster**

```bash
# Install the Pod Identity Agent addon (one-time per cluster)
aws eks create-addon \
  --cluster-name my-cluster \
  --addon-name eks-pod-identity-agent \
  --region us-west-2
```

This deploys a DaemonSet that intercepts AWS API calls from pods and injects temporary credentials.

**Step 2: Create an IAM Role (Standard Trust Policy)**

```json
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Service": "pods.eks.amazonaws.com"
    },
    "Action": [
      "sts:AssumeRole",
      "sts:TagSession"
    ]
  }]
}
```

Notice the trust policy is simpler — no OIDC provider ARN needed.

**Step 3: Create a Pod Identity Association**

```bash
aws eks create-pod-identity-association \
  --cluster-name my-cluster \
  --namespace my-namespace \
  --service-account my-service-account \
  --role-arn arn:aws:iam::123456789012:role/my-pod-role \
  --region us-west-2
```

This creates a mapping between your ServiceAccount and IAM role. No annotations needed on the ServiceAccount itself.

**Step 4: Use the ServiceAccount in Your Pod (Same as IRSA)**

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-app
  namespace: my-namespace
spec:
  serviceAccountName: my-service-account
  containers:
  - name: app
    image: my-app:latest
```

The Pod Identity Agent automatically provides credentials to any pod using that ServiceAccount.

## Real-World Example: Logging to S3 with Fluent Bit

Let's set up Fluent Bit to ship logs to S3 without hardcoded credentials. We'll use **Pod Identity** since it's cleaner.

**1. Create the IAM Role and Policy**

```bash
# Create trust policy file
cat > trust-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"Service": "pods.eks.amazonaws.com"},
    "Action": ["sts:AssumeRole", "sts:TagSession"]
  }]
}
EOF

# Create the role
aws iam create-role \
  --role-name fluent-bit-s3-role \
  --assume-role-policy-document file://trust-policy.json

# Attach S3 write permissions
aws iam attach-role-policy \
  --role-name fluent-bit-s3-role \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess  # Tighten this in production
```

**2. Create the ServiceAccount**

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluent-bit
  namespace: logging
```

**3. Create Pod Identity Association**

```bash
aws eks create-pod-identity-association \
  --cluster-name production-cluster \
  --namespace logging \
  --service-account fluent-bit \
  --role-arn arn:aws:iam::123456789012:role/fluent-bit-s3-role \
  --region us-west-2
```

**4. Deploy Fluent Bit with the ServiceAccount**

```yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit
  namespace: logging
spec:
  selector:
    matchLabels:
      app: fluent-bit
  template:
    metadata:
      labels:
        app: fluent-bit
    spec:
      serviceAccountName: fluent-bit  # Fluent Bit gets S3 permissions here
      containers:
      - name: fluent-bit
        image: fluent/fluent-bit:2.0
        env:
        - name: AWS_REGION
          value: us-west-2
        volumeMounts:
        - name: config
          mountPath: /fluent-bit/etc/
      volumes:
      - name: config
        configMap:
          name: fluent-bit-config
```

Fluent Bit will now write logs to S3 using temporary credentials fetched from the Pod Identity Agent. No AWS keys anywhere in your cluster.

## Best Practices

1. **One ServiceAccount per workload with distinct permissions** — Don't share a single SA across unrelated apps just because they need similar AWS access. Create separate SAs and roles so you can audit and revoke permissions independently. If your API server and cron jobs both need S3 access, give them different ServiceAccounts tied to different roles.

2. **Use session tags for attribution in CloudTrail** — When creating Pod Identity associations, add session tags like `team`, `environment`, and `app-name`. These appear in CloudTrail logs and help track which pod made which API call during an incident. With IRSA, you get the service account name automatically in the `sourceIdentity` field.

3. **Scope IAM policies tightly to resource ARNs** — Never use `Resource: "*"` in IAM policies. Pin permissions to specific S3 buckets, DynamoDB tables, or SQS queues. If a pod only reads from `s3://logs-prod-usw2/app-logs/`, don't give it access to `s3://logs-*/*`. Wildcards are audit failures waiting to happen.

4. **Rotate roles, not credentials** — Since temporary credentials expire every 60 minutes, your security posture improves without manual rotation. But if a role is compromised, you still need to delete the Pod Identity association or IRSA annotation, then recreate the role with a new ARN. Automate this with Terraform or CloudFormation.

5. **Monitor failed AssumeRole attempts in CloudTrail** — Set up an alarm for `sts:AssumeRoleWithWebIdentity` errors. These indicate misconfigured trust policies, deleted ServiceAccounts, or pods trying to assume roles they shouldn't have. Most breaches start with reconnaissance via failed AWS API calls.

6. **Use eksctl for IRSA if you're not automating with IaC** — The `eksctl` CLI has a `create iamserviceaccount` command that sets up the OIDC provider, creates the IAM role, and annotates the ServiceAccount in one shot. It's faster than manual AWS CLI steps and harder to misconfigure. For Pod Identity, there's no shortcut — use the AWS CLI or Terraform.

## Conclusion

IRSA and Pod Identity eliminate the security nightmare of static AWS credentials in Kubernetes. IRSA has been battle-tested for years and works everywhere. Pod Identity is newer but simpler — if you're on EKS 1.24+, it's the better choice for new projects. Both give you pod-level IAM permissions that auto-rotate and leave clean audit trails.

**Key Takeaways:**
- IRSA uses OIDC federation; Pod Identity uses an EKS-managed agent. Both achieve the same result — temporary, scoped credentials.
- Pod Identity requires EKS 1.24+ but needs no OIDC provider setup. IRSA works on older clusters but has more moving parts.
- Always scope IAM policies to specific resources, not `*`. Generic permissions defeat the purpose of pod-level isolation.
- Session tags and CloudTrail monitoring are critical for tracking which pod made which AWS call during incidents.
- For legacy clusters, stick with IRSA. For greenfield EKS deployments, use Pod Identity.

Start by migrating your highest-risk workloads first — anything touching S3, Secrets Manager, or DynamoDB. Once you see how much cleaner the setup is compared to static keys, you'll wonder why you didn't do this years ago.

## Frequently Asked Questions

**Q: Can I use IRSA and Pod Identity on the same cluster?**  
Yes, but don't mix them for the same workload. If you have legacy apps using IRSA, leave them alone. Use Pod Identity for new deployments. EKS handles both mechanisms in parallel without conflicts.

**Q: What happens if my pod needs access to multiple AWS services?**  
Create a single IAM role with policies for all required services and attach it to one ServiceAccount. Don't create multiple ServiceAccounts per pod — Kubernetes only allows one ServiceAccount per pod spec.

**Q: Do IRSA credentials work outside EKS (like in local development)?**  
No. IRSA and Pod Identity only work inside EKS because they rely on EKS injecting the web identity token or the Pod Identity Agent. For local dev, use AWS profiles or environment variables with short-lived credentials from `aws sts assume-role`.

**Q: How do I troubleshoot "AccessDenied" errors with IRSA?**  
Check three things: (1) Is the ServiceAccount annotated correctly with the role ARN? (2) Does the IAM role's trust policy match the exact namespace and ServiceAccount name? (3) Are the IAM permissions broad enough for the API call? Use `kubectl describe pod` to verify the token is injected at `/var/run/secrets/eks.amazonaws.com/serviceaccount/`.

**Q: Can I revoke a pod's AWS access without restarting it?**  
With Pod Identity, delete the association — the next time the pod requests credentials, it'll be denied. With IRSA, remove the ServiceAccount annotation and delete the pod (credentials are cached for up to an hour in the pod). For immediate revocation, add an explicit Deny policy to the IAM role targeting the pod's session name.]]></content><author><name>Sushil Verma</name></author><category term="Kubernetes" /><category term="AWS" /><category term="eks" /><category term="irsa" /><category term="pod-identity" /><category term="iam" /><category term="kubernetes" /><category term="aws" /><category term="security" /><category term="rbac" /><summary type="html"><![CDATA[Eliminate hardcoded AWS credentials in EKS by using IRSA and Pod Identity for secure, temporary IAM permissions at the pod level.]]></summary></entry><entry><title type="html">Writing Resume: Quantifying Infrastructure Impact with Metrics and Cost Savings</title><link href="http://localhost:4000/quantifying-infrastructure-impact-metrics-cost-savings/" rel="alternate" type="text/html" title="Writing Resume: Quantifying Infrastructure Impact with Metrics and Cost Savings" /><published>2026-06-22T10:00:00+05:30</published><updated>2026-06-22T10:00:00+05:30</updated><id>http://localhost:4000/quantifying-infrastructure-impact-metrics-cost-savings</id><content type="html" xml:base="http://localhost:4000/quantifying-infrastructure-impact-metrics-cost-savings/"><![CDATA[<img src="/assets/images/blogs/writing-resume-quantifying-infrastructure-impact-metrics.svg" alt="Writing Resume: Quantifying Infrastructure Impact - Dashboard showing metrics, cost savings, and performance improvements for resume achievements" width="1050" height="520">

You just finished migrating 50 services to Kubernetes. Your resume says "Migrated services to Kubernetes." The hiring manager yawns and moves to the next candidate. Why? Because every infrastructure engineer says they "migrated services" or "improved performance." What they don't know is whether you saved $10 or $100,000, cut deployment time by 5 minutes or 5 hours, or impacted 10 users or 10 million.

## What Is Quantified Infrastructure Impact?

**Quantified infrastructure impact** means translating technical work into measurable business outcomes using specific metrics, percentages, and dollar amounts. Instead of describing what you did, you prove what changed because of your work.

**Why Quantifying Impact Matters:**

- **Hiring managers think in ROI:** They need to justify your salary. Showing you saved $200K makes that easy.
- **Differentiates you from generic candidates:** "Improved performance" is noise. "Reduced API latency from 800ms to 120ms (85% faster)" is signal.
- **Demonstrates business acumen:** You understand that infrastructure exists to serve business goals, not just for technical elegance.
- **Makes achievements memorable:** Numbers stick in memory far better than vague descriptions.
- **Passes ATS keyword filters:** Specific metrics often contain keywords that automated systems prioritize.
- **Enables comparison:** Hiring managers can benchmark your impact against other candidates objectively.

## How to Identify Quantifiable Metrics

### System Performance Metrics

Track changes in system behavior before and after your work:

```yaml
# Performance Metrics to Track
Response Time:
  - API latency (p50, p95, p99)
  - Page load time
  - Database query time
  - Build/deploy duration

Throughput:
  - Requests per second
  - Transactions processed
  - Data pipeline volume
  - Concurrent users supported

Reliability:
  - Uptime percentage (99.9% → 99.99%)
  - MTTR (Mean Time To Recovery)
  - Error rate reduction
  - Failed deployment percentage

Efficiency:
  - CPU/Memory utilization
  - Storage optimization
  - Network bandwidth reduction
  - Container density per node
```

**Example transformation:**

❌ **Before:** "Optimized database queries"  
✅ **After:** "Optimized 15 critical database queries, reducing average response time from 850ms to 180ms (79% faster) and cutting database load by 60%"

### Cost Savings Metrics

Infrastructure work often directly impacts cloud spend and operational costs:

```python
# Cost Calculation Example
def calculate_annual_savings(old_monthly_cost, new_monthly_cost):
    """
    Calculate annual savings from infrastructure optimization
    """
    monthly_savings = old_monthly_cost - new_monthly_cost
    annual_savings = monthly_savings * 12
    percentage_reduction = (monthly_savings / old_monthly_cost) * 100
    
    return {
        'annual_savings': f'${annual_savings:,.0f}',
        'monthly_savings': f'${monthly_savings:,.0f}',
        'percentage': f'{percentage_reduction:.1f}%'
    }

# Real example: EC2 to ECS migration
old_cost = 18000  # $18K/month on EC2
new_cost = 7500   # $7.5K/month on ECS

result = calculate_annual_savings(old_cost, new_cost)
# Output: {'annual_savings': '$126,000', 'monthly_savings': '$10,500', 'percentage': '58.3%'}
```

**Cost metrics to track:**

- **Cloud spend:** EC2, RDS, S3, data transfer costs
- **License reduction:** Eliminated unnecessary software licenses
- **Operational overhead:** Reduced manual intervention hours
- **Downtime cost avoidance:** Revenue protected by improved uptime
- **Team efficiency:** Hours saved per week across the team

### Scale and Scope Metrics

Show the magnitude of what you managed or impacted:

```markdown
Scale Indicators:
- Number of services/applications
- User base size (internal or external)
- Data volume (TB processed, stored)
- Geographic distribution (regions, data centers)
- Team size you enabled or supported
- Deployment frequency (weekly → daily)

Example statement:
"Built Terraform modules used by 8 teams to provision infrastructure
for 120+ microservices serving 2.5M daily active users across 4 AWS regions"
```

## Real-World Examples: Weak vs Strong

### Example 1: Kubernetes Migration

❌ **Weak:** "Migrated applications to Kubernetes cluster"

✅ **Strong:** "Migrated 43 Node.js microservices from EC2 to EKS, reducing deployment time from 45 minutes to 8 minutes (82% faster), cutting infrastructure costs by $9,200/month (42% reduction), and enabling 12 deployments/day (up from 2/day)"

**Why it works:** Three quantified outcomes (time, cost, deployment frequency) with specific numbers and percentages.

### Example 2: CI/CD Pipeline

❌ **Weak:** "Implemented CI/CD pipeline for faster deployments"

✅ **Strong:** "Built GitLab CI/CD pipeline with automated testing and canary deployments, reducing production incidents by 68% (from 22 to 7 monthly) and enabling 45 daily deployments across 6 teams (previously 8 manual deployments/week)"

**Why it works:** Shows reliability improvement AND team velocity impact with before/after comparison.

### Example 3: Monitoring Implementation

❌ **Weak:** "Set up monitoring and alerting"

✅ **Strong:** "Deployed Prometheus + Grafana monitoring for 85 services with 120+ custom dashboards and PagerDuty integration, reducing mean time to detection (MTTD) from 23 minutes to 90 seconds and cutting MTTR from 2.5 hours to 35 minutes"

**Why it works:** Uses industry-standard SRE metrics (MTTD, MTTR) that hiring managers recognize and value.

### Example 4: Infrastructure as Code

❌ **Weak:** "Wrote Terraform code for infrastructure provisioning"

✅ **Strong:** "Developed 25 reusable Terraform modules for VPC, EKS, RDS, and S3, adopted by 6 engineering teams to provision consistent infrastructure in 8 minutes (down from 3+ hours of manual setup), eliminating configuration drift across 3 AWS accounts"

**Why it works:** Quantifies reusability, time savings, and organizational impact beyond your immediate team.

## Putting It All Together: Complete Bullet Points

Here's how to structure impactful resume bullets:

```markdown
Formula: [Action Verb] + [What] + [How] + [Quantified Result] + [Business Impact]

Examples:

1. "Architected auto-scaling ECS infrastructure for payment processing service,
   handling 10M+ daily transactions with 99.98% uptime, processing $50M+ annually
   while reducing compute costs by $180K/year through spot instance strategy"

2. "Implemented HashiCorp Vault for secrets management across 60+ applications,
   eliminating hardcoded credentials, achieving SOC 2 compliance, and reducing
   security incident response time by 75% (from 4 hours to 1 hour)"

3. "Built self-service Kubernetes platform using ArgoCD and Kustomize for 12 teams,
   reducing environment provisioning from 2 weeks to 20 minutes and enabling
   300+ deployments/week (10x increase) with zero-touch production releases"

4. "Migrated legacy monolith to containerized microservices on EKS, improving
   API response time by 83% (2.3s → 400ms), increasing deployment frequency
   from monthly to daily, and cutting infrastructure spend by $15K/month"

5. "Designed disaster recovery strategy with cross-region RDS replication and
   automated failover, achieving RPO of 5 minutes and RTO of 15 minutes,
   protecting $2M+ monthly revenue with 99.95% availability SLA"
```

## How to Gather Metrics Retroactively

**Problem:** "I did this work 6 months ago and didn't track metrics!"

**Solution:** Reconstruct approximate metrics from available data:

```bash
# Sources to mine for historical metrics

# 1. Cloud billing dashboards
# Check AWS Cost Explorer, Azure Cost Management, or GCP Billing
# Compare costs month-over-month around your project timeline

# 2. Monitoring tools
# Query Grafana, Datadog, CloudWatch for historical performance data
# Example: Compare average response times pre/post deployment

# 3. CI/CD pipeline logs
# Check Jenkins, GitLab, GitHub Actions for:
- Build duration trends
- Deployment frequency
- Test execution time

# 4. Ticketing systems (Jira, ServiceNow)
# Count incidents before vs after your improvements:
SELECT COUNT(*), MONTH(created_date)
FROM incidents
WHERE created_date BETWEEN '2025-01-01' AND '2026-06-01'
GROUP BY MONTH(created_date);

# 5. Git history
git log --since="6 months ago" --pretty=format:"%h %ad %s" --date=short | grep -i "deploy\|release" | wc -l
# Count deployment commits to measure velocity

# 6. Teammate estimates
# Ask team members: "Before I built X, how long did Y take?"
# Collective memory often provides reasonable approximations
```

> **Pro Tip:** If you can't get exact numbers, use conservative estimates with qualifiers: "Reduced deployment time by approximately 60% (from ~45 minutes to ~18 minutes based on team feedback)" is far better than no quantification at all.

## Best Practices for Quantifying Impact

1. **Always include the "before" state** — "Reduced from X to Y" is more powerful than "achieved Y" because it shows improvement magnitude.

2. **Use percentages AND absolute numbers** — "Reduced costs by 45% ($78K annually)" gives both relative and absolute context.

3. **Pick metrics that matter to business** — Millisecond improvements are impressive to engineers, but "increased checkout conversion by 2.1% ($450K additional revenue)" speaks to executives.

4. **Be specific with timeframes** — "Reduced deployment time from 2 hours to 15 minutes" tells you nothing about frequency. Add "enabling 8 deployments/day vs 2/week" for full context.

5. **Stack multiple metrics** — Strong bullets combine speed + cost + reliability: "Migrated to EKS, cutting deploy time 70%, reducing costs $12K/month, improving uptime from 99.2% to 99.9%".

6. **Avoid vanity metrics** — "Wrote 10,000 lines of Terraform" means nothing. "Deployed infrastructure in 5 minutes vs 3 hours" shows actual value.

## Conclusion

Quantifying infrastructure impact transforms your resume from a task list into a compelling story of business value creation. Hiring managers don't care that you "managed Kubernetes clusters"—they care that you "reduced infrastructure costs by $240K while improving deployment velocity 5x and achieving 99.99% uptime for services handling 50M requests/day."

**Key Takeaways:**

- Replace vague verbs with specific metrics: percentages, dollar amounts, time saved, scale reached
- Use the formula: What + How + Quantified Result + Business Impact
- Track performance, cost, and scale metrics as you complete projects—don't rely on memory
- Include before/after comparisons and percentages alongside absolute numbers
- Focus on metrics that translate to business value, not just technical achievements

Start auditing your resume right now. Find every bullet point that lacks numbers. Can you add response times? Cost savings? Deployment frequency? Team impact? The difference between a phone screen and an onsite interview often comes down to whether you can prove your infrastructure work actually mattered.

## Frequently Asked Questions

**Q: What if my company culture doesn't share cost information with engineers?**

You can still use performance metrics (latency, throughput, uptime), scale metrics (number of services, users, requests), and efficiency metrics (deployment frequency, recovery time). For cost estimates, use public AWS/GCP pricing calculators with your known instance types and counts to create reasonable approximations. Mark these as estimates: "Estimated $50K annual savings based on EC2 pricing for 12 instances migrated to ECS."

**Q: Is it okay to round numbers for readability?**

Yes, within reason. "$127,432 annual savings" becomes "$127K annually" or even "~$130K annually." Avoid over-rounding: "over $100K" loses impact compared to "$127K." For very large numbers, "$2.3M" is clearer than "$2,300,000." Never inflate numbers, but reasonable rounding for readability is standard practice.

**Q: How do I quantify work on greenfield projects with no "before" state?**

Focus on scale, capability, and enabling metrics: "Built CI/CD platform enabling 15 teams to deploy 200+ times/week with 8-minute average pipeline duration" or "Architected multi-region Kubernetes platform supporting 80 microservices across 3 AWS regions with 99.95% SLA." Also compare against industry baselines: "Achieved sub-100ms API latency (3x faster than industry median of 300ms)."

**Q: What if I can't remember exact metrics from projects 2 years ago?**

Use conservative estimates with qualifiers. "Reduced deployment time by approximately 70% (from roughly 40 minutes to 12 minutes)" is acceptable. Check old performance reviews, project retrospectives, or team wikis for documented metrics. Reach out to former colleagues who might have saved metrics. If reconstruction is impossible, focus on qualitative impact for older work and commit to tracking metrics religiously going forward.

**Q: Should I quantify impact differently for startup vs enterprise roles?**

Yes. Startups value speed, scrappiness, and business impact: "Built MVP infrastructure on $800/month budget, scaling to 100K users in 3 months." Enterprises value scale, compliance, and risk reduction: "Achieved SOC 2 compliance for 40-application portfolio, passed audit with zero findings." Also, early-stage startups care about revenue impact ("infrastructure changes increased checkout conversion by 3%, adding $80K MRR"), while enterprises focus on cost savings and risk mitigation.]]></content><author><name>Sushil Verma</name></author><category term="Career" /><category term="Resume" /><category term="resume" /><category term="career" /><category term="infrastructure" /><category term="metrics" /><category term="cost-optimization" /><category term="impact" /><summary type="html"><![CDATA[Transform vague infrastructure achievements into quantified impact statements that hiring managers can't ignore with metrics, cost savings, and business outcomes.]]></summary></entry><entry><title type="html">Site Reliability Engineering (SRE) Guide: SRE vs DevOps, SLOs, and Error Budgets</title><link href="http://localhost:4000/site-reliability-engineering-guide-sre-vs-devops-error-budgets/" rel="alternate" type="text/html" title="Site Reliability Engineering (SRE) Guide: SRE vs DevOps, SLOs, and Error Budgets" /><published>2026-05-28T10:00:00+05:30</published><updated>2026-05-28T10:00:00+05:30</updated><id>http://localhost:4000/site-reliability-engineering-guide-sre-vs-devops-error-budgets</id><content type="html" xml:base="http://localhost:4000/site-reliability-engineering-guide-sre-vs-devops-error-budgets/"><![CDATA[<img src="/assets/images/blogs/site-reliability-engineering-guide-sre-vs-devops-error-budgets.svg" alt="Site Reliability Engineering Guide" width="1050" height="520">

Site Reliability Engineering (SRE) is what happens when you ask a software engineer to design an operations function. Coined by Google in 2003, SRE applies software engineering principles — automation, measurement, iterative improvement — to infrastructure and operations. It is not a rebranding of DevOps. It is a specific implementation of DevOps principles with concrete practices, metrics, and organizational structures.

## SRE vs DevOps

| Aspect | DevOps | SRE |
|---|---|---|
| Origin | Cultural movement | Google engineering practice |
| Focus | Breaking silos, collaboration | Reliability as a feature |
| Measurement | Deployment frequency, lead time | SLOs, error budgets |
| Key practice | CI/CD, IaC, monitoring | Error budgets, toil reduction |
| Team structure | Embedded in dev teams | Dedicated SRE team |
| Philosophy | "Everyone is responsible for ops" | "Software engineers do ops, using software" |

**They are complementary.** DevOps is the philosophy. SRE is one way to implement it.

## SLIs, SLOs, and SLAs

### SLI — Service Level Indicator

A quantitative measure of a service's behavior. The raw metric.

| SLI Type | Formula | Example |
|---|---|---|
| Availability | `successful_requests / total_requests` | 99.95% of requests return non-5xx |
| Latency | `requests_under_threshold / total_requests` | 95% of requests complete in < 300ms |
| Throughput | `requests_processed / time_period` | 10,000 requests per second |
| Error rate | `error_requests / total_requests` | < 0.1% of requests return errors |

### SLO — Service Level Objective

The target for an SLI. This is the reliability target your team commits to.

```
SLO: 99.9% of requests to the payment API will return
successfully (non-5xx) over a 30-day rolling window.
```

### SLA — Service Level Agreement

A contract with consequences (usually financial) for missing the SLO. SLAs are for customers. SLOs are for internal engineering.

**Key insight:** Your SLO should be stricter than your SLA. If your SLA is 99.9%, your SLO should be 99.95%. This gives you a buffer.

## Error Budgets

The error budget is the amount of unreliability you can tolerate.

```
Error Budget = 1 - SLO

If SLO = 99.9%:
Error Budget = 0.1% = 43.8 minutes of downtime per month
                     = 8.76 hours per year
```

### How Error Budgets Work

1. **Budget remaining:** Ship features. Deploy aggressively. Take risks.
2. **Budget exhausted:** Freeze feature releases. Focus on reliability work only.

This creates a self-regulating system:
- Developers want to ship features → they need error budget → they care about reliability
- SREs want reliability → they allow feature releases when budget is available → they support velocity

### Error Budget Policy

```
When error budget is:
  > 50% remaining → Ship freely, deploy daily
  25-50% remaining → Deploy with extra testing, no risky changes
  < 25% remaining → Deploy only reliability fixes
  0% remaining → Feature freeze until budget replenishes
```

## Toil: The Enemy of SRE

**Toil** is manual, repetitive, automatable work that scales linearly with service growth and has no enduring value.

**Examples of toil:**
- Manually restarting pods that crash weekly
- Hand-creating user accounts
- Copy-pasting the same Terraform for every new environment
- Manually checking certificate expiration dates

**Not toil:**
- Incident response (unpredictable, requires judgment)
- Writing automation (enduring value)
- Architecture reviews (strategic value)

**The 50% rule:** SREs should spend no more than 50% of their time on toil. The rest goes to engineering work that reduces future toil.

### Measuring Toil

Track time spent on toil weekly:

| Week | Toil Hours | Engineering Hours | Toil % |
|---|---|---|---|
| 1 | 25 | 15 | 63% ⚠️ |
| 4 | 20 | 20 | 50% |
| 8 | 12 | 28 | 30% ✅ |
| 12 | 8 | 32 | 20% ✅ |

## Incident Management

### Incident Lifecycle

```
Detection → Triage → Mitigation → Resolution → Post-mortem
```

### Severity Levels

| Level | Impact | Response |
|---|---|---|
| SEV1 | Service down, revenue impact | All hands, 15-min response |
| SEV2 | Degraded performance, partial outage | On-call + backup, 30-min response |
| SEV3 | Minor impact, workaround available | On-call, next business day |
| SEV4 | No user impact, internal only | Track in backlog |

### Blameless Post-Mortems

Every SEV1 and SEV2 incident gets a post-mortem within 48 hours.

**Template:**
1. **Summary:** What happened, when, impact.
2. **Timeline:** Minute-by-minute events.
3. **Root cause:** The underlying issue (not "human error").
4. **Contributing factors:** What made it worse.
5. **Action items:** Concrete fixes with owners and deadlines.
6. **Lessons learned:** What we will do differently.

**Critical rule:** No blame. If a human made a mistake, the question is "Why did our system allow this mistake to happen?" — not "Who made the mistake?"

## On-Call

### Sustainable On-Call

- **Minimum 2 people in rotation.** No single point of failure.
- **8-12 hour shifts,** not 24-hour shifts.
- **Compensation:** Time off after on-call shifts, or additional pay.
- **Escalation path:** If the on-call engineer cannot resolve in 30 minutes, escalate.
- **Runbooks:** Every alert has a runbook with diagnosis steps and resolution procedures.
- **Target:** < 2 pages per 12-hour shift. More than that means your system needs reliability work.

## Building an SRE Practice

### Start Small

You do not need a dedicated SRE team to start. Adopt SRE practices incrementally.

**Month 1-2:** Define SLIs and SLOs for your most critical service.

**Month 3-4:** Implement error budget tracking. Dashboard the error budget burn rate.

**Month 5-6:** Introduce blameless post-mortems for all incidents.

**Month 7-8:** Measure toil. Set a target to reduce it by 20%.

**Month 9-12:** Formalize on-call rotation. Create runbooks for every alert.

### SRE Team Models

| Model | Structure | Best For |
|---|---|---|
| Embedded | SRE sits within a dev team | Small org, 1-3 SREs |
| Dedicated | Separate SRE team | Medium org, 4-10 SREs |
| Consulting | SRE reviews and advises | Large org, SRE expertise as a service |

## Conclusion

SRE is not about preventing all failures — it is about measuring reliability, setting targets, and making data-driven trade-offs between reliability and velocity. Error budgets give teams a shared language: "We have budget, so let us ship." Or "We are out of budget, so let us fix reliability." Start by defining SLOs for your most important service, track the error budget, and run blameless post-mortems. The rest follows naturally.

---

**Related posts:**
- [The Complete Guide to Observability](/complete-guide-observability-metrics-logs-traces-opentelemetry/)
- [How to Monitor Kubernetes](/how-to-monitor-kubernetes-prometheus-grafana-step-by-step/)
- [Platform Engineering in 2026](/platform-engineering-2026-complete-guide-internal-developer-platforms/)]]></content><author><name>Sushil Verma</name></author><category term="DevOps" /><category term="SRE" /><category term="sre" /><category term="site-reliability" /><category term="devops" /><category term="slo" /><category term="sli" /><category term="error-budget" /><category term="observability" /><category term="reliability" /><summary type="html"><![CDATA[SRE applies software engineering to operations. This pillar guide covers SRE vs DevOps, SLOs, SLIs, error budgets, incident management, toil reduction, and building an SRE practice.]]></summary></entry><entry><title type="html">DevOps Home Lab Setup in 2026: Practice Kubernetes, Terraform, and CI/CD for Free</title><link href="http://localhost:4000/devops-home-lab-setup-2026-practice-kubernetes-terraform-cicd/" rel="alternate" type="text/html" title="DevOps Home Lab Setup in 2026: Practice Kubernetes, Terraform, and CI/CD for Free" /><published>2026-05-25T10:00:00+05:30</published><updated>2026-05-25T10:00:00+05:30</updated><id>http://localhost:4000/devops-home-lab-setup-2026-practice-kubernetes-terraform-cicd</id><content type="html" xml:base="http://localhost:4000/devops-home-lab-setup-2026-practice-kubernetes-terraform-cicd/"><![CDATA[<img src="/assets/images/blogs/devops-home-lab-setup-2026-practice-kubernetes-terraform-cicd.svg" alt="DevOps Home Lab Setup in 2026" width="1050" height="520">

You do not need an AWS account to learn DevOps. A laptop with 16GB of RAM (8GB minimum) can run Kubernetes, Terraform, CI/CD pipelines, monitoring stacks, and GitOps — all locally, all free. This guide builds a complete DevOps lab from scratch.

## What You Will Build

```
┌─────────────────────────────────────────────┐
│               Your Laptop                    │
│                                              │
│  ┌─────────────┐  ┌──────────────────────┐  │
│  │   Gitea     │  │   Minikube / k3d     │  │
│  │  (Git host) │  │  (Kubernetes)        │  │
│  └──────┬──────┘  │  ┌────────────────┐  │  │
│         │         │  │ Your App       │  │  │
│  ┌──────▼──────┐  │  │ ArgoCD         │  │  │
│  │   Drone CI  │  │  │ Prometheus     │  │  │
│  │  (CI/CD)    │──│  │ Grafana        │  │  │
│  └─────────────┘  │  │ Loki           │  │  │
│                    │  └────────────────┘  │  │
│  ┌─────────────┐  └──────────────────────┘  │
│  │ Terraform   │                             │
│  │ (IaC for    │  ┌──────────────────────┐  │
│  │  Docker     │  │   Docker Compose     │  │
│  │  resources) │  │  (databases, Redis)  │  │
│  └─────────────┘  └──────────────────────┘  │
└─────────────────────────────────────────────┘
```

## Prerequisites

| Tool | Purpose | Install |
|---|---|---|
| Docker Desktop | Container runtime | `brew install --cask docker` |
| kubectl | Kubernetes CLI | `brew install kubectl` |
| Helm | Kubernetes package manager | `brew install helm` |
| Terraform | Infrastructure as Code | `brew install terraform` |
| k3d or Minikube | Local Kubernetes | `brew install k3d` |

## Step 1: Local Kubernetes Cluster

### Option A: k3d (Recommended — lightweight)

```bash
# Create a cluster with 3 nodes and port mapping
k3d cluster create devlab   --servers 1   --agents 2   --port "8080:80@loadbalancer"   --port "8443:443@loadbalancer"

# Verify
kubectl get nodes
# NAME                  STATUS   ROLES                  AGE
# k3d-devlab-server-0   Ready    control-plane,master   30s
# k3d-devlab-agent-0    Ready    <none>                 25s
# k3d-devlab-agent-1    Ready    <none>                 25s
```

### Option B: Minikube

```bash
minikube start --cpus=4 --memory=8192 --driver=docker
minikube addons enable ingress
minikube addons enable metrics-server
```

## Step 2: Deploy a Sample Application

```yaml
# sample-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello
  template:
    metadata:
      labels:
        app: hello
    spec:
      containers:
        - name: hello
          image: hashicorp/http-echo
          args: ["-text=Hello from DevOps Home Lab!"]
          ports:
            - containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
  name: hello-app
spec:
  selector:
    app: hello
  ports:
    - port: 80
      targetPort: 5678
```

```bash
kubectl apply -f sample-app.yaml
kubectl port-forward svc/hello-app 9090:80
# Visit http://localhost:9090
```

## Step 3: Monitoring Stack (Prometheus + Grafana)

```bash
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install monitoring prometheus-community/kube-prometheus-stack   --namespace monitoring --create-namespace   --set grafana.adminPassword=admin

# Access Grafana
kubectl port-forward -n monitoring svc/monitoring-grafana 3000:80
# Visit http://localhost:3000 (admin/admin)
```

You now have Prometheus scraping cluster metrics and Grafana dashboards pre-configured for Kubernetes.

## Step 4: GitOps with ArgoCD

```bash
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Get admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

# Port forward
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Visit https://localhost:8080 (admin/<password>)
```

Create a Git repository with your Kubernetes manifests. ArgoCD watches the repo and automatically deploys changes. Push a change → ArgoCD deploys it → observe in the dashboard.

## Step 5: Terraform with Docker Provider

Practice Terraform locally using the Docker provider (no cloud account needed).

```hcl
# main.tf
terraform {
  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 3.0"
    }
  }
}

provider "docker" {}

resource "docker_image" "nginx" {
  name = "nginx:alpine"
}

resource "docker_container" "web" {
  name  = "terraform-nginx"
  image = docker_image.nginx.image_id
  ports {
    internal = 80
    external = 8081
  }
}
```

```bash
terraform init
terraform plan
terraform apply
# Visit http://localhost:8081
terraform destroy
```

## Step 6: CI/CD with Gitea + Drone (Self-Hosted)

```yaml
# docker-compose.yml
services:
  gitea:
    image: gitea/gitea:latest
    ports:
      - "3001:3000"
      - "2222:22"
    volumes:
      - gitea-data:/data

  drone:
    image: drone/drone:latest
    ports:
      - "3002:80"
    environment:
      - DRONE_GITEA_SERVER=http://gitea:3000
      - DRONE_SERVER_HOST=localhost:3002
      - DRONE_SERVER_PROTO=http
    volumes:
      - drone-data:/data

  drone-runner:
    image: drone/drone-runner-docker:latest
    environment:
      - DRONE_RPC_HOST=drone
      - DRONE_RPC_PROTO=http
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

volumes:
  gitea-data:
  drone-data:
```

Push code to Gitea → Drone builds and tests automatically.

## Step 7: Log Aggregation with Loki

```bash
helm install loki grafana/loki-stack   --namespace monitoring   --set promtail.enabled=true   --set loki.persistence.enabled=false
```

Add Loki as a data source in Grafana. Now you can query logs from all pods alongside metrics.

## Practice Projects

| Project | Skills Practiced |
|---|---|
| Deploy a 3-tier app (frontend, API, DB) | K8s manifests, Services, ConfigMaps |
| Set up GitOps pipeline | ArgoCD, Git workflows |
| Create Terraform modules | IaC, modules, state management |
| Build a CI/CD pipeline | Drone/GitHub Actions, Docker builds |
| Implement monitoring + alerting | Prometheus, Grafana, PromQL |
| Blue/green deployment | K8s Deployments, Service switching |
| Secrets management | K8s Secrets, Sealed Secrets |

## Conclusion

A DevOps home lab eliminates the barrier to practice. No cloud bills, no corporate environment restrictions. Just your laptop and Docker. Start with a k3d cluster and a sample app, add monitoring, then layer in GitOps and CI/CD. The skills you build here transfer directly to production cloud environments. Every tool in this lab has a production equivalent — k3d becomes EKS, Gitea becomes GitHub, Drone becomes GitHub Actions. The concepts are identical.

---

**Related posts:**
- [How to Learn DevOps in 2026](/how-to-learn-devops-2026-complete-roadmap-zero-to-senior-engineer/)
- [How to Get Your First DevOps Job](/how-to-get-first-devops-job-2026/)
- [Kubernetes YAML Cheat Sheet](/kubernetes-yaml-cheat-sheet-every-manifest-you-need/)]]></content><author><name>Sushil Verma</name></author><category term="DevOps" /><category term="Learning" /><category term="home-lab" /><category term="devops" /><category term="kubernetes" /><category term="terraform" /><category term="ci-cd" /><category term="docker" /><category term="learning" /><category term="free" /><summary type="html"><![CDATA[Build a complete DevOps home lab on your laptop for free. Practice Kubernetes, Terraform, CI/CD, monitoring, and GitOps without spending on cloud resources.]]></summary></entry><entry><title type="html">Serverless vs Containers in 2026: When to Use Which</title><link href="http://localhost:4000/serverless-vs-containers-2026-when-to-use-which/" rel="alternate" type="text/html" title="Serverless vs Containers in 2026: When to Use Which" /><published>2026-05-21T10:00:00+05:30</published><updated>2026-05-21T10:00:00+05:30</updated><id>http://localhost:4000/serverless-vs-containers-2026-when-to-use-which</id><content type="html" xml:base="http://localhost:4000/serverless-vs-containers-2026-when-to-use-which/"><![CDATA[<img src="/assets/images/blogs/serverless-vs-containers-2026-when-to-use-which.svg" alt="Serverless vs Containers in 2026" width="1050" height="520">

Serverless and containers are not competing technologies — they are different points on a spectrum of abstraction. Serverless hides the infrastructure entirely. Containers give you control over the runtime. The right choice depends on your workload, team, and operational maturity.

## The Spectrum

```
More Control                                    Less Control
More Ops Overhead                              Less Ops Overhead
     │                                              │
     ▼                                              ▼
  Bare Metal → VMs → Containers → Fargate → Lambda
                        (K8s/ECS)   (Serverless    (Serverless
                                     containers)    functions)
```

## Head-to-Head Comparison

| Factor | Containers (K8s/ECS) | Serverless (Lambda/Fargate) |
|---|---|---|
| Scaling | Configure auto-scaling rules | Automatic, instant |
| Cold start | None | 100ms-10s (depending on runtime) |
| Max execution | Unlimited | 15 min (Lambda), unlimited (Fargate) |
| Pricing | Pay for allocated resources | Pay per invocation/duration |
| Ops overhead | Cluster management, patching | Near-zero |
| Debugging | Standard tools, SSH into container | CloudWatch, limited visibility |
| State | Volumes, local storage | Stateless by design |
| Portability | Docker image runs anywhere | Vendor lock-in |

## When to Use Serverless

### 1. Event-Driven Processing
```
S3 Upload → Lambda → Process Image → Store in DynamoDB
API Gateway → Lambda → Query DB → Return JSON
SQS Message → Lambda → Send Email
```

Short, stateless, event-triggered work. Lambda handles scaling, concurrency, and infrastructure.

### 2. Variable or Unpredictable Traffic

If your traffic goes from 0 to 10,000 requests per second and back to 0, serverless scales automatically and costs nothing at zero traffic. Containers would require pre-provisioned capacity.

### 3. Small Team, No DevOps

A team of 3 developers building an API does not need Kubernetes. API Gateway + Lambda + DynamoDB gives a fully managed, scalable backend with zero infrastructure management.

### 4. Glue Logic and Automation

- CloudWatch Event → Lambda → Resize EBS volumes
- S3 Event → Lambda → Generate thumbnails
- DynamoDB Stream → Lambda → Update search index

## When to Use Containers

### 1. Long-Running Processes

Web servers, WebSocket connections, background workers that run continuously. Lambda's 15-minute limit does not work here.

### 2. High and Steady Traffic

At consistent high throughput, containers are cheaper than Lambda. Lambda charges per invocation and duration. Containers charge for allocated resources regardless of usage.

**Break-even example:**
- Lambda: 10M requests/month × 200ms × 512MB = ~$35/month
- Fargate: 1 vCPU + 2GB running 24/7 = ~$36/month

At 100M requests/month, Lambda costs ~$350. Fargate still costs ~$36. Containers win at high, steady load.

### 3. Complex Runtimes

Applications with specific OS dependencies, custom binaries, ML models loaded into memory, or GPU requirements need containers.

### 4. Portability Requirements

Docker images run on AWS, Azure, GCP, or on-premises. Lambda functions are AWS-specific (or cloud-specific for equivalent services).

### 5. Team Has Kubernetes Expertise

If your team already operates Kubernetes, running workloads on K8s is natural. The ops overhead is already paid.

## The Hybrid Approach

Most production systems use both:

```
┌──────────────────────────────────┐
│          API Gateway             │
│    (request routing + auth)      │
└──────────┬───────────────────────┘
           │
    ┌──────┴──────┐
    │             │
    ▼             ▼
┌────────┐  ┌──────────┐
│ Lambda │  │ Fargate  │
│        │  │          │
│ Auth   │  │ Core API │
│ Resize │  │ WebSocket│
│ Webhook│  │ ML Model │
│ Cron   │  │ Workers  │
└────────┘  └──────────┘
```

- **Lambda** for event-driven, short-lived, variable-traffic functions
- **Fargate/Containers** for long-running, steady-traffic, complex services
- **API Gateway** as the unified entry point

## AWS Service Selection Guide

| Workload | Best Service | Why |
|---|---|---|
| REST API, low-medium traffic | API Gateway + Lambda | Zero ops, auto-scaling |
| REST API, high steady traffic | Fargate or ECS | Cost-effective at scale |
| WebSocket | Fargate or ECS | Lambda has 15-min limit |
| Batch processing | Lambda or AWS Batch | Lambda for <15 min, Batch for longer |
| ML inference | Fargate (GPU) or SageMaker | GPU support, model loading |
| Cron jobs | EventBridge + Lambda | Simple, no idle cost |
| Event processing | Lambda + SQS/SNS | Native integration |

## Conclusion

Choose serverless when you want to minimize ops, handle variable traffic, or build event-driven systems. Choose containers when you need long-running processes, steady high-throughput, portability, or complex runtimes. Most production architectures use both — serverless for the edges and event processing, containers for the core services. The decision is not ideological; it is economic and operational.

---

**Related posts:**
- [Kubernetes vs Docker](/kubernetes-vs-docker-2026-complete-comparison/)
- [Dockerfile Best Practices](/dockerfile-best-practices-build-production-ready-images/)
- [AWS vs Azure vs GCP in 2026](/aws-vs-azure-vs-gcp-2026-which-cloud-platform-learn-first/)]]></content><author><name>Sushil Verma</name></author><category term="Cloud" /><category term="DevOps" /><category term="serverless" /><category term="containers" /><category term="lambda" /><category term="fargate" /><category term="kubernetes" /><category term="cloud" /><category term="architecture" /><summary type="html"><![CDATA[Serverless and containers solve different problems. This guide compares AWS Lambda vs Fargate vs ECS vs Kubernetes across cost, scaling, cold starts, and operational complexity.]]></summary></entry><entry><title type="html">Zero Trust Security for Cloud Infrastructure: A Complete Guide</title><link href="http://localhost:4000/zero-trust-security-cloud-infrastructure-complete-guide/" rel="alternate" type="text/html" title="Zero Trust Security for Cloud Infrastructure: A Complete Guide" /><published>2026-05-18T10:00:00+05:30</published><updated>2026-05-18T10:00:00+05:30</updated><id>http://localhost:4000/zero-trust-security-cloud-infrastructure-complete-guide</id><content type="html" xml:base="http://localhost:4000/zero-trust-security-cloud-infrastructure-complete-guide/"><![CDATA[<img src="/assets/images/blogs/zero-trust-security-cloud-infrastructure-complete-guide.svg" alt="Zero Trust Security for Cloud Infrastructure" width="1050" height="520">

The traditional security model — a strong perimeter with trusted insiders — does not work in the cloud. There is no perimeter. Your workloads run across multiple regions, developers access systems from home, and APIs are exposed to the internet. Zero Trust replaces the perimeter model with a simple principle: **never trust, always verify.** Every request, from every user and every service, must be authenticated and authorized, regardless of where it originates.

## Core Principles of Zero Trust

### 1. Verify Explicitly

Every access request must be authenticated and authorized based on all available signals:
- **Identity:** Who is making the request?
- **Device:** Is the device compliant and healthy?
- **Location:** Where is the request coming from?
- **Service:** Which service is calling which service?
- **Data sensitivity:** What classification is the resource?

### 2. Least Privilege Access

Grant the minimum permissions needed for the task, for the minimum time needed.

```json
{
  "Effect": "Allow",
  "Action": ["s3:GetObject"],
  "Resource": "arn:aws:s3:::reports-bucket/quarterly/*",
  "Condition": {
    "IpAddress": {"aws:SourceIp": "10.0.0.0/8"},
    "DateLessThan": {"aws:CurrentTime": "2026-06-01T00:00:00Z"}
  }
}
```

### 3. Assume Breach

Design systems as if the attacker is already inside. Minimize the blast radius of any compromise through microsegmentation, encryption, and continuous monitoring.

## Identity: The New Perimeter

### Service-to-Service Authentication

Every microservice must authenticate when calling another service. No service trusts another by default.

**Mutual TLS (mTLS):**
```yaml
# Istio PeerAuthentication — enforce mTLS for all services in namespace
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: production
spec:
  mtls:
    mode: STRICT
```

**SPIFFE/SPIRE:** The Secure Production Identity Framework for Everyone assigns cryptographic identities to workloads. No static secrets.

### Human Access

- **SSO + MFA everywhere.** No local accounts on cloud consoles.
- **Short-lived credentials.** Use AWS IAM Identity Center (SSO), Azure AD, or GCP Workforce Identity Federation. No long-lived access keys.
- **Just-in-time access.** Engineers request elevated access for a specific task. Access expires automatically.

```bash
# AWS: Assume a role with a 1-hour session (not permanent keys)
aws sts assume-role   --role-arn arn:aws:iam::123456789012:role/deploy-prod   --role-session-name "deploy-2026-05"   --duration-seconds 3600
```

## Network: Microsegmentation

### Replace VPN with Zero Trust Network Access (ZTNA)

Traditional VPN gives access to the entire network. ZTNA gives access to specific applications based on identity and device posture.

| VPN | ZTNA |
|---|---|
| Network-level access | Application-level access |
| Trust once, access everything | Verify every request |
| Full network visibility | Only authorized apps visible |
| Static rules | Context-aware policies |

### Kubernetes Network Policies

```yaml
# Only frontend can talk to API, only API can talk to database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-policy
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes: [Ingress, Egress]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend
      ports:
        - port: 8080
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: postgres
      ports:
        - port: 5432
    - to:  # Allow DNS
        - namespaceSelector: {}
      ports:
        - port: 53
          protocol: UDP
```

## Data Protection

- **Encrypt everything at rest.** S3, EBS, RDS, DynamoDB — use KMS with customer-managed keys.
- **Encrypt everything in transit.** TLS 1.2+ for all connections. No exceptions.
- **Classify data.** Not all data needs the same protection. PII and financial data get stricter controls.
- **Tokenize sensitive data.** Replace credit card numbers with tokens. Store the real values in a vault.

## Continuous Monitoring

Zero Trust requires continuous verification, not one-time authentication.

- **Security Information and Event Management (SIEM):** Aggregate logs from all services. Detect anomalies.
- **Cloud Security Posture Management (CSPM):** Continuously check cloud configurations against best practices.
- **Runtime security:** Tools like Falco detect unexpected behavior in containers (new process spawned, sensitive file accessed).

```yaml
# Falco rule: detect shell in container
- rule: Terminal shell in container
  desc: A shell was spawned in a container
  condition: >
    spawned_process and container and
    proc.name in (bash, sh, zsh)
  output: >
    Shell spawned in container
    (container=%container.name command=%proc.cmdline)
  priority: WARNING
```

## Implementation Roadmap

| Phase | Duration | Actions |
|---|---|---|
| 1. Inventory | 2 weeks | Map all identities, services, data flows, and access patterns |
| 2. Identity | 4 weeks | SSO, MFA, short-lived credentials, service mesh mTLS |
| 3. Network | 4 weeks | Microsegmentation, network policies, VPC endpoints |
| 4. Data | 2 weeks | Encryption at rest/transit, data classification |
| 5. Monitoring | 2 weeks | SIEM, CSPM, runtime detection |
| 6. Continuous | Ongoing | Regular access reviews, policy updates, incident response drills |

## Conclusion

Zero Trust is not a product you buy — it is an architecture you build. Start with identity (the new perimeter), enforce least privilege, segment your network, encrypt your data, and monitor everything continuously. The goal is not to prevent all breaches — it is to minimize the blast radius when one occurs. In the cloud, where there is no perimeter to defend, Zero Trust is the only security model that works.

---

**Related posts:**
- [Container Security Best Practices](/container-security-best-practices-2026/)
- [DevSecOps Pipeline](/devsecops-pipeline-adding-security-to-cicd/)
- [Kubernetes YAML Cheat Sheet](/kubernetes-yaml-cheat-sheet-every-manifest-you-need/)]]></content><author><name>Sushil Verma</name></author><category term="Security" /><category term="Cloud" /><category term="zero-trust" /><category term="security" /><category term="cloud" /><category term="identity" /><category term="network" /><category term="devops" /><category term="IAM" /><summary type="html"><![CDATA[Zero Trust means never trust, always verify. This guide covers identity-based access, microsegmentation, least privilege, and practical implementation for cloud infrastructure.]]></summary></entry><entry><title type="html">AWS Solutions Architect Associate Exam Prep 2026: Complete Study Guide</title><link href="http://localhost:4000/aws-solutions-architect-exam-prep-2026-complete-study-guide/" rel="alternate" type="text/html" title="AWS Solutions Architect Associate Exam Prep 2026: Complete Study Guide" /><published>2026-05-14T10:00:00+05:30</published><updated>2026-05-14T10:00:00+05:30</updated><id>http://localhost:4000/aws-solutions-architect-exam-prep-2026-complete-study-guide</id><content type="html" xml:base="http://localhost:4000/aws-solutions-architect-exam-prep-2026-complete-study-guide/"><![CDATA[<img src="/assets/images/blogs/aws-solutions-architect-exam-prep-2026-complete-study-guide.svg" alt="AWS Solutions Architect Exam Prep 2026" width="1050" height="520">

The AWS Solutions Architect Associate (SAA-C03) is the most popular cloud certification in the world. It validates your ability to design cost-effective, scalable, and resilient architectures on AWS. This guide covers exactly what you need to study, how to study it, and what to expect on exam day.

## Exam Overview

| Detail | Value |
|---|---|
| Exam code | SAA-C03 |
| Duration | 130 minutes |
| Questions | 65 (multiple choice + multiple response) |
| Passing score | 720/1000 |
| Cost | $150 USD |
| Validity | 3 years |

## The Four Domains

| Domain | Weight | Focus |
|---|---|---|
| 1. Secure Architectures | 30% | IAM, encryption, VPC security |
| 2. Resilient Architectures | 26% | Multi-AZ, auto-scaling, DR |
| 3. High-Performing Architectures | 24% | Caching, database selection, compute |
| 4. Cost-Optimized Architectures | 20% | Instance pricing, storage tiers, right-sizing |

## Domain 1: Secure Architectures (30%)

### IAM Deep Dive

- **Users, Groups, Roles, Policies.** Roles are for services and cross-account access. Never use root account keys.
- **Policy evaluation:** Explicit deny > explicit allow > implicit deny.
- **Least privilege:** Start with no permissions, add only what is needed.
- **Resource-based policies** (S3 bucket policies, SQS queue policies) vs identity-based policies.

### Encryption

| Data State | Service | Method |
|---|---|---|
| At rest | S3 | SSE-S3, SSE-KMS, SSE-C |
| At rest | EBS | KMS-managed keys |
| At rest | RDS | KMS encryption (enable at creation) |
| In transit | All | TLS 1.2+ (ALB, CloudFront, API Gateway) |

**KMS key types:** AWS managed (free, auto-rotated), Customer managed (you control rotation, policies), Customer provided (SSE-C, you manage keys entirely).

### VPC Security

- **Security Groups:** Stateful, allow rules only, attached to ENI.
- **NACLs:** Stateless, allow + deny rules, attached to subnets.
- **VPC Flow Logs:** Capture IP traffic metadata for analysis.
- **PrivateLink / VPC Endpoints:** Access AWS services without internet.

## Domain 2: Resilient Architectures (26%)

### High Availability Patterns

- **Multi-AZ:** RDS Multi-AZ, ELB across AZs, Auto Scaling Group across AZs.
- **Multi-Region:** Route 53 failover, S3 Cross-Region Replication, DynamoDB Global Tables.

### Auto Scaling

- **Target tracking:** "Keep average CPU at 60%." Simplest and most common.
- **Step scaling:** "Add 2 instances when CPU > 80%, add 4 when CPU > 90%."
- **Scheduled scaling:** "Scale to 10 instances every Monday at 9 AM."
- **Predictive scaling:** ML-based, predicts traffic patterns.

### Disaster Recovery

| Strategy | RTO | RPO | Cost |
|---|---|---|---|
| Backup & Restore | Hours | Hours | $ |
| Pilot Light | Minutes | Minutes | $$ |
| Warm Standby | Minutes | Seconds | $$$ |
| Multi-Site Active/Active | Near-zero | Near-zero | $$$$ |

**Exam tip:** Match the DR strategy to the RTO/RPO requirements in the question. Cost-conscious = Backup & Restore. Mission-critical = Multi-Site.

## Domain 3: High-Performing Architectures (24%)

### Compute Selection

| Workload | Best Service |
|---|---|
| Web applications | EC2, Fargate, Lambda |
| Batch processing | AWS Batch, Spot Instances |
| Containers | ECS (Fargate or EC2), EKS |
| Event-driven | Lambda |
| HPC | EC2 (placement groups, EFA) |

### Database Selection

| Need | Best Service |
|---|---|
| Relational, SQL | RDS (Aurora for performance) |
| Key-value, high throughput | DynamoDB |
| In-memory cache | ElastiCache (Redis or Memcached) |
| Graph data | Neptune |
| Time-series | Timestream |
| Document | DocumentDB |
| Data warehouse | Redshift |

### Caching

- **CloudFront:** CDN for static/dynamic content. Use for global distribution.
- **ElastiCache:** Application-level caching (session store, query cache).
- **DAX:** DynamoDB-specific caching (microsecond latency).

## Domain 4: Cost-Optimized Architectures (20%)

### EC2 Pricing

| Model | Discount | Commitment | Best For |
|---|---|---|---|
| On-Demand | 0% | None | Variable, unpredictable |
| Reserved | 30-60% | 1 or 3 years | Steady-state baseline |
| Savings Plan | 30-60% | $/hour commitment | Flexible compute |
| Spot | 60-90% | None (can be interrupted) | Fault-tolerant, batch |

### S3 Storage Classes

| Class | Use Case | Cost |
|---|---|---|
| S3 Standard | Frequent access | $$$ |
| S3 Standard-IA | Infrequent, quick access | $$ |
| S3 One Zone-IA | Non-critical infrequent | $ |
| S3 Glacier Instant | Archive, millisecond access | $ |
| S3 Glacier Flexible | Archive, minutes-hours | ¢ |
| S3 Glacier Deep Archive | Long-term archive, 12h retrieval | ¢ |
| S3 Intelligent-Tiering | Unknown access patterns | Auto-optimized |

## Study Plan (6-8 Weeks)

| Week | Focus | Activities |
|---|---|---|
| 1-2 | Core services (EC2, S3, VPC, IAM) | Video course + hands-on labs |
| 3-4 | Databases, caching, serverless | Build a sample app on AWS |
| 5-6 | Architecture patterns, DR, security | AWS Well-Architected Labs |
| 7 | Practice exams | 3-4 full practice tests |
| 8 | Review weak areas | Re-study topics where practice scores < 80% |

## Study Resources

1. **Official:** AWS Skill Builder (free courses), AWS Well-Architected Labs
2. **Video courses:** Stephane Maarek (Udemy), Adrian Cantrill, Neal Davis
3. **Practice exams:** Tutorials Dojo (Jon Bonso) — closest to real exam
4. **Hands-on:** Build projects in the AWS Free Tier
5. **Whitepapers:** Well-Architected Framework, Disaster Recovery, Security Best Practices

## Exam Day Tips

1. **Flag and skip hard questions.** Answer easy ones first, return to flagged ones.
2. **Eliminate wrong answers.** Most questions have 1-2 obviously wrong options.
3. **Look for keywords:** "most cost-effective" = cheapest option that works. "highest availability" = Multi-AZ/Multi-Region. "least operational overhead" = managed/serverless.
4. **Time management:** 2 minutes per question. If stuck after 90 seconds, flag and move on.
5. **Read the full question.** AWS questions often have a key constraint in the last sentence.

## Conclusion

The SAA-C03 is achievable in 6-8 weeks of focused study. Understand the services deeply rather than memorizing — the exam tests architectural thinking. Build real projects on AWS to reinforce concepts, take multiple practice exams, and focus your final review on weak domains. The certification opens doors to cloud architecture roles and demonstrates your ability to design on AWS.

---

**Related posts:**
- [AWS vs Azure vs GCP in 2026](/aws-vs-azure-vs-gcp-2026-which-cloud-platform-learn-first/)
- [Cloud Cost Optimization](/cloud-cost-optimization-how-we-cut-aws-bill-60-percent/)
- [FinOps for Engineers](/finops-for-engineers-understanding-reducing-cloud-spend/)]]></content><author><name>Sushil Verma</name></author><category term="AWS" /><category term="Cloud" /><category term="aws" /><category term="solutions-architect" /><category term="certification" /><category term="exam-prep" /><category term="cloud" /><category term="study-guide" /><summary type="html"><![CDATA[A focused study guide for the AWS Solutions Architect Associate exam (SAA-C03). Covers every domain, key services, architecture patterns, and study strategies.]]></summary></entry><entry><title type="html">Kubernetes YAML Cheat Sheet: Every Manifest You Need</title><link href="http://localhost:4000/kubernetes-yaml-cheat-sheet-every-manifest-you-need/" rel="alternate" type="text/html" title="Kubernetes YAML Cheat Sheet: Every Manifest You Need" /><published>2026-05-11T10:00:00+05:30</published><updated>2026-05-11T10:00:00+05:30</updated><id>http://localhost:4000/kubernetes-yaml-cheat-sheet-every-manifest-you-need</id><content type="html" xml:base="http://localhost:4000/kubernetes-yaml-cheat-sheet-every-manifest-you-need/"><![CDATA[<img src="/assets/images/blogs/kubernetes-yaml-cheat-sheet-every-manifest-you-need.svg" alt="Kubernetes YAML Cheat Sheet" width="1050" height="520">

Kubernetes configuration is YAML. Every resource — Pods, Deployments, Services, Ingress — is defined in a YAML manifest. This cheat sheet gives you production-ready templates for every common resource type. Copy, paste, customize.

## Pod

The smallest deployable unit. Rarely created directly — use a Deployment instead.

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: debug-pod
  labels:
    app: debug
spec:
  containers:
    - name: debug
      image: busybox:1.36
      command: ["sleep", "3600"]
      resources:
        requests:
          cpu: 100m
          memory: 128Mi
        limits:
          cpu: 200m
          memory: 256Mi
```

## Deployment

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  labels:
    app: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: myregistry/api:1.2.0
          ports:
            - containerPort: 8080
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: url
            - name: LOG_LEVEL
              valueFrom:
                configMapKeyRef:
                  name: api-config
                  key: log-level
          resources:
            requests:
              cpu: 250m
              memory: 512Mi
            limits:
              cpu: 500m
              memory: 1Gi
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 20
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - key: app
                      operator: In
                      values: [api]
                topologyKey: kubernetes.io/hostname
```

## Service (ClusterIP)

```yaml
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector:
    app: api
  ports:
    - port: 80
      targetPort: 8080
      protocol: TCP
  type: ClusterIP
```

## Service (NodePort)

```yaml
apiVersion: v1
kind: Service
metadata:
  name: api-nodeport
spec:
  selector:
    app: api
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30080
  type: NodePort
```

## Service (LoadBalancer)

```yaml
apiVersion: v1
kind: Service
metadata:
  name: api-lb
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: nlb
spec:
  selector:
    app: api
  ports:
    - port: 443
      targetPort: 8080
  type: LoadBalancer
```

## Ingress (Nginx)

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - api.example.com
      secretName: api-tls
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api
                port:
                  number: 80
```

## ConfigMap

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: api-config
data:
  log-level: "info"
  max-connections: "100"
  config.yaml: |
    server:
      port: 8080
      timeout: 30s
    cache:
      ttl: 300s
```

## Secret

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
stringData:
  url: postgresql://user:password@db:5432/mydb
  username: user
  password: password
```

> **Note:** In production, use an external secrets manager (AWS Secrets Manager, HashiCorp Vault) with the External Secrets Operator instead of storing secrets in YAML.

## HorizontalPodAutoscaler

```yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
```

## Job

```yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
spec:
  backoffLimit: 3
  ttlSecondsAfterFinished: 86400
  template:
    spec:
      restartPolicy: OnFailure
      containers:
        - name: migrate
          image: myregistry/migrator:1.0
          command: ["./migrate", "--up"]
          envFrom:
            - secretRef:
                name: db-credentials
```

## CronJob

```yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: nightly-backup
spec:
  schedule: "0 2 * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 3
  jobTemplate:
    spec:
      backoffLimit: 2
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: backup
              image: myregistry/backup:1.0
              command: ["./backup.sh"]
              volumeMounts:
                - name: backup-storage
                  mountPath: /backups
          volumes:
            - name: backup-storage
              persistentVolumeClaim:
                claimName: backup-pvc
```

## PersistentVolumeClaim

```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: gp3
  resources:
    requests:
      storage: 50Gi
```

## NetworkPolicy

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-netpol
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend
      ports:
        - port: 8080
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: postgres
      ports:
        - port: 5432
```

## ServiceAccount + RBAC

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: api-sa
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: api-role
rules:
  - apiGroups: [""]
    resources: ["configmaps", "secrets"]
    verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: api-rolebinding
subjects:
  - kind: ServiceAccount
    name: api-sa
roleRef:
  kind: Role
  name: api-role
  apiGroup: rbac.authorization.k8s.io
```

## Quick Reference

| Resource | Kind | Common Use |
|---|---|---|
| Pod | Pod | Debugging, one-off tasks |
| Deployment | Deployment | Stateless apps |
| StatefulSet | StatefulSet | Databases, stateful apps |
| DaemonSet | DaemonSet | Node agents, log collectors |
| Job | Job | One-time tasks, migrations |
| CronJob | CronJob | Scheduled tasks |
| Service | Service | Internal/external networking |
| Ingress | Ingress | HTTP routing, TLS termination |
| ConfigMap | ConfigMap | Non-sensitive configuration |
| Secret | Secret | Sensitive data |
| PVC | PersistentVolumeClaim | Storage requests |
| HPA | HorizontalPodAutoscaler | Auto-scaling |
| NetworkPolicy | NetworkPolicy | Pod-to-pod firewall rules |
| ServiceAccount | ServiceAccount | Pod identity, RBAC |

---

**Related posts:**
- [Kubernetes vs Docker](/kubernetes-vs-docker-2026-complete-comparison/)
- [Docker Compose vs Kubernetes](/docker-compose-vs-kubernetes-when-to-use-what/)
- [How to Monitor Kubernetes](/how-to-monitor-kubernetes-prometheus-grafana-step-by-step/)]]></content><author><name>Sushil Verma</name></author><category term="Kubernetes" /><category term="DevOps" /><category term="kubernetes" /><category term="k8s" /><category term="yaml" /><category term="cheat-sheet" /><category term="manifests" /><category term="devops" /><summary type="html"><![CDATA[A copy-paste-ready cheat sheet of every Kubernetes YAML manifest you need — Pods, Deployments, Services, Ingress, ConfigMaps, Secrets, Jobs, CronJobs, and more.]]></summary></entry></feed>