Category: Devops

How to Deploy Kubernetes on AWS the Scalable Way

Kubernetes has become the de facto standard for orchestrating containerized workloads—but deploying it correctly on AWS requires more than just spinning up an EKS cluster. You need to think about scalability, cost-efficiency, security, and high availability from day one.

In this guide, we’ll walk you through how to deploy a scalable, production-grade Kubernetes environment on AWS—step by step.

Why Kubernetes on AWS?

Amazon Web Services offers powerful tools to run Kubernetes at scale, including:

  • Amazon EKS – Fully managed control plane
  • EC2 Auto Scaling Groups – Dynamic compute scaling
  • Elastic Load Balancer (ELB) – Handles incoming traffic
  • IAM Roles for Service Accounts – Fine-grained access control
  • Fargate (Optional) – Run pods without managing servers

Step-by-Step Deployment Plan

1. Plan the Architecture

Your Kubernetes architecture should be:

  • Highly Available (Multi-AZ)
  • Scalable (Auto-scaling groups)
  • Secure (Private networking, IAM roles)
  • Observable (Monitoring, logging)
+---------------------+
|   Route 53 / ALB    |
+----------+----------+
           |
   +-------v-------+
   |  EKS Control   |
   |    Plane       |  <- Managed by AWS
   +-------+--------+
           |
+----------v----------+
|   EC2 Worker Nodes   |  <- Auto-scaling
|  (in Private Subnet) |
+----------+-----------+
           |
   +-------v--------+
   |  Kubernetes     |
   |  Workloads      |
   +-----------------+

2. Provision Infrastructure with IaC (Terraform)

Use Terraform to define your VPC, subnets, security groups, and EKS cluster:

module "eks" {
  source          = "terraform-aws-modules/eks/aws"
  cluster_name    = "my-cluster"
  cluster_version = "1.29"
  subnets         = module.vpc.private_subnets
  vpc_id          = module.vpc.vpc_id
  manage_aws_auth = true

  node_groups = {
    default = {
      desired_capacity = 3
      max_capacity     = 6
      min_capacity     = 1
      instance_type    = "t3.medium"
    }
  }
}

Security Tip: Keep worker nodes in private subnets and expose only your load balancer to the public internet.

3. Set Up Cluster Autoscaler

Install the Kubernetes Cluster Autoscaler to automatically scale your EC2 nodes:

kubectl apply -f cluster-autoscaler-autodiscover.yaml

Ensure the autoscaler has IAM permissions via IRSA (IAM Roles for Service Accounts).

4. Use Horizontal Pod Autoscaler

Use HPA to scale pods based on resource usage:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: myapp-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myapp
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

5. Implement CI/CD Pipelines

Use tools like Argo CD, Flux, or GitHub Actions:

- name: Deploy to EKS
  uses: aws-actions/amazon-eks-deploy@v1
  with:
    cluster-name: my-cluster
    kubectl-version: '1.29'

6. Set Up Observability

Install:

  • Prometheus + Grafana for metrics
  • Fluent Bit or Loki for logging
  • Kube-State-Metrics for cluster state
  • AWS CloudTrail and GuardDuty for security monitoring

7. Optimize Costs

  • Use Spot Instances with on-demand fallback
  • Use EC2 Mixed Instance Policies
  • Try Graviton (ARM) nodes for better cost-performance ratio

Bonus: Fargate Profiles for Microservices

For small or bursty workloads, use AWS Fargate to run pods serverlessly:

eksctl create fargateprofile \
  --cluster my-cluster \
  --name fp-default \
  --namespace default

Recap Checklist

  • Multi-AZ VPC with private subnets
  • Terraform-managed EKS cluster
  • Cluster and pod auto-scaling enabled
  • CI/CD pipeline in place
  • Observability stack (metrics/logs/security)
  • Spot instances or Fargate to save costs

Final Thoughts

Deploying Kubernetes on AWS at scale doesn’t have to be complex—but it does need a solid foundation. Use managed services where possible, automate everything, and focus on observability and security from the start.

If you’re looking for a production-grade, scalable deployment, Terraform + EKS + autoscaling is your winning combo.

What the Hell is Helm? (And Why You Should Care)

What the Hell is Helm?

If you’re tired of managing 10+ YAML files for every app, Helm is your new best friend. It’s basically the package manager for Kubernetes — like apt or brew — but for deploying apps in your cluster.

Instead of editing raw YAML over and over for each environment (dev, staging, prod), Helm lets you template it, inject dynamic values, and install with a single command.


Why Use Helm?

Here’s the reality:

  • You don’t want to maintain 3 sets of YAMLs for each environment.
  • You want to roll back fast if something breaks.
  • You want to reuse deployments across projects without rewriting.

Helm fixes all that. It gives you:

  • Templated YAML (no more copy-paste hell)
  • One chart, many environments
  • Version control + rollback support
  • Easy upgrades with helm upgrade
  • Access to thousands of ready-made charts from the community

Real Talk: What’s a Chart?

Think of a chart like a folder of YAML files with variables in it. You install it, pass in your config (values.yaml), and Helm renders the final manifests and applies them to your cluster.

When you install a chart, Helm creates a release — basically, a named instance of the chart running in your cluster.


How to Get Started (No BS)

1. Install Helm

brew install helm       # mac  
choco install kubernetes-helm  # windows  
sudo snap install helm  # linux  

2. Create Your First Chart

helm create myapp

Boom — you now have a scaffolded chart in a folder with templates, a values file, and everything else you need.


Folder Breakdown

myapp/
├── Chart.yaml         # Metadata
├── values.yaml        # Config you can override
└── templates/         # All your actual Kubernetes YAMLs (as templates)

Example Template (deployment.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-app
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Chart.Name }}
  template:
    metadata:
      labels:
        app: {{ .Chart.Name }}
    spec:
      containers:
      - name: {{ .Chart.Name }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        ports:
        - containerPort: 80

Example values.yaml

replicaCount: 3
image:
  repository: nginx
  tag: latest

Change the values, re-deploy, and you’re done.


Deploying to Your Cluster

helm install my-release ./myapp

Upgrading later?

helm upgrade my-release ./myapp -f prod-values.yaml

Roll it back?

helm rollback my-release 1

Uninstall it?

helm uninstall my-release

Simple. Clean. Versioned.


Want a Database?

Don’t write your own MySQL config. Just pull it from Bitnami’s chart repo:

helm repo add bitnami https://charts.bitnami.com/bitnami
helm install my-db bitnami/mysql

Done.


Helm lets you:

  • Turn your Kubernetes YAML into reusable templates
  • Manage config per environment without duplicating files
  • Version your deployments and roll back instantly
  • Install apps like MySQL, Redis, etc., with one command

It’s the smart way to scale your Kubernetes setup without losing your mind.

More Cheat Sheet for DevOps Engineers

More Cheat Sheet for DevOps Engineers

This guide is focused entirely on the most commonly used Kubernetes YAML examples and why you’d use them in a production or staging environment. These YAML definitions act as the foundation for automating, scaling, and managing containerized workloads.


1. Pod YAML (Basic Unit of Execution)

Use this when you want to run a single container on the cluster.

apiVersion: v1
kind: Pod
metadata:
  name: simple-pod
spec:
  containers:
  - name: nginx
    image: nginx

This is the most basic unit in Kubernetes. Ideal for testing and debugging.


2. Deployment YAML (For Scaling and Updates)

Use deployments to manage stateless apps with rolling updates and replicas.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: nginx
        image: nginx:1.21

3. Production-Ready Deployment Example

Use this to deploy a resilient application with health checks and resource limits.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: production-app
  labels:
    app: myapp
spec:
  replicas: 4
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp-container
        image: myorg/myapp:2.1.0
        ports:
        - containerPort: 80
        livenessProbe:
          httpGet:
            path: /healthz
            port: 80
          initialDelaySeconds: 15
          periodSeconds: 20
        readinessProbe:
          httpGet:
            path: /ready
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 10
        resources:
          requests:
            cpu: "250m"
            memory: "512Mi"
          limits:
            cpu: "500m"
            memory: "1Gi"

4. Service YAML (Stable Networking Access)

apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 80
  type: ClusterIP

5. ConfigMap YAML (External Configuration)

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: "debug"
  FEATURE_FLAG: "true"

6. Secret YAML (Sensitive Information)

apiVersion: v1
kind: Secret
metadata:
  name: app-secret
stringData:
  password: supersecret123

7. PersistentVolumeClaim YAML (For Storage)

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

8. Job YAML (Run Once Tasks)

apiVersion: batch/v1
kind: Job
metadata:
  name: hello-job
spec:
  template:
    spec:
      containers:
      - name: hello
        image: busybox
        command: ["echo", "Hello World"]
      restartPolicy: Never

9. CronJob YAML (Recurring Tasks)

apiVersion: batch/v1
kind: CronJob
metadata:
  name: scheduled-task
spec:
  schedule: "*/5 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: task
            image: busybox
            args: ["/bin/sh", "-c", "echo Scheduled Job"]
          restartPolicy: OnFailure

10. Ingress YAML (Routing External Traffic)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-service
            port:
              number: 80

11. NetworkPolicy YAML (Security Control)

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-nginx
spec:
  podSelector:
    matchLabels:
      app: nginx
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend

Cheat Sheet for DevOps Engineers

kubectl Cheat Sheet – In-Depth Guide

Managing Kubernetes clusters efficiently requires fluency with kubectl, the command-line interface for interacting with the Kubernetes API. Whether you’re deploying applications, viewing logs, or debugging infrastructure, this tool is your gateway to smooth cluster operations.

This in-depth cheat sheet will give you a comprehensive reference of how to use kubectl effectively in real-world operations, including advanced flags, filtering tricks, rolling updates, patching, output formatting, and resource exploration.


Shell Autocompletion

Boost productivity with shell autocompletion for kubectl:

Bash

source <(kubectl completion bash)
echo "source <(kubectl completion bash)" >> ~/.bashrc

ZSH

source <(kubectl completion zsh)
echo '[[ $commands[kubectl] ]] && source <(kubectl completion zsh)' >> ~/.zshrc

Aliases

alias k=kubectl
complete -o default -F __start_kubectl k

Working with Contexts & Clusters

kubectl config view                        # Show merged config
kubectl config get-contexts               # List contexts
kubectl config current-context            # Show current context
kubectl config use-context my-context     # Switch to context

Set default namespace for future commands:

kubectl config set-context --current --namespace=my-namespace

Deployments & YAML Management

kubectl apply -f ./manifest.yaml          # Apply resource(s)
kubectl apply -f ./dir/                   # Apply all YAMLs in directory
kubectl create deployment nginx --image=nginx
kubectl explain pod                       # Show pod schema

Apply resources from multiple sources:

kubectl apply -f ./one.yaml -f ./two.yaml
kubectl apply -f https://example.com/config.yaml

Viewing & Finding Resources

kubectl get pods                          # List all pods
kubectl get pods -o wide                  # Detailed pod listing
kubectl get services                      # List services
kubectl describe pod my-pod               # Detailed pod info

Filter & sort:

kubectl get pods --field-selector=status.phase=Running
kubectl get pods --sort-by='.status.containerStatuses[0].restartCount'

Find pod labels:

kubectl get pods --show-labels

Updating & Rolling Deployments

kubectl set image deployment/web nginx=nginx:1.19       # Set new image
kubectl rollout status deployment/web                   # Watch rollout
kubectl rollout history deployment/web                  # Show revisions
kubectl rollout undo deployment/web                     # Undo last change
kubectl rollout undo deployment/web --to-revision=2     # Revert to specific

Editing, Scaling, Deleting

kubectl edit deployment/web                    # Live edit YAML
kubectl scale deployment/web --replicas=5      # Scale up/down
kubectl delete pod my-pod                      # Delete by name
kubectl delete -f pod.yaml                     # Delete from file

Logs, Execs & Debugging

kubectl logs my-pod                            # View logs
kubectl logs my-pod -c container-name          # Logs from container
kubectl logs my-pod --previous                 # Logs from crashed
kubectl exec my-pod -- ls /                    # Run command
kubectl exec -it my-pod -- bash                # Shell access

Working with Services

kubectl expose pod nginx --port=80 --target-port=8080   # Create service
kubectl port-forward svc/my-service 8080:80             # Forward to local

Resource Metrics

kubectl top pod                              # Pod metrics
kubectl top pod --sort-by=cpu                # Sort by CPU
kubectl top node                             # Node metrics

Patching Resources

Strategic merge patch:

kubectl patch deployment my-deploy -p '{"spec":{"replicas":4}}'

JSON patch with array targeting:

kubectl patch pod my-pod --type='json' -p='[
  {"op": "replace", "path": "/spec/containers/0/image", "value":"nginx:1.21"}
]'

Cluster & Node Management

kubectl cordon my-node                        # Prevent new pods
kubectl drain my-node                         # Evict pods for maintenance
kubectl uncordon my-node                      # Resume scheduling
kubectl get nodes                             # List all nodes
kubectl describe node my-node                 # Node details

Output Formatting

kubectl get pods -o json
kubectl get pods -o yaml
kubectl get pods -o custom-columns="NAME:.metadata.name,IMAGE:.spec.containers[*].image"

Exploring API Resources

kubectl api-resources                         # List all resources
kubectl api-resources --namespaced=false      # Non-namespaced
kubectl api-resources -o wide                 # Extended info

Logging & Verbosity

kubectl get pods -v=6                         # Debug output
kubectl get deployment -v=9                   # Full API trace

Deployment Template Example

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: my-namespace
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80

0