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)
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
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
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