EKS Production Architecture Overview
EKS (Elastic Kubernetes Service) runs Kubernetes on an AWS-managed control plane. In production, the standard combination is Multi-AZ node groups + Karpenter autoscaling + ALB Ingress.
Prerequisites — Installing Tools
Bash
# eksctl
curl -sL "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
sudo mv /tmp/eksctl /usr/local/bin
# kubectl
curl -LO "https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl && sudo mv kubectl /usr/local/bin
# helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# check
eksctl version && kubectl version --client && helm versionCreating a Cluster — Declarative Configuration with eksctl
YAML
# cluster.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: prod-cluster
region: ap-northeast-2
version: "1.29"
iam:
withOIDC: true # IRSA(IAM Roles for Service Accounts)
managedNodeGroups:
- name: system
instanceType: m6i.large
minSize: 2
maxSize: 4
desiredCapacity: 2
availabilityZones: [ap-northeast-2a, ap-northeast-2b, ap-northeast-2c]
labels:
role: system
taints:
- key: CriticalAddonsOnly
value: "true"
effect: NoSchedule
iam:
attachPolicyARNs:
- arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
- arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
- arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
addons:
- name: vpc-cni
version: latest
- name: coredns
version: latest
- name: kube-proxy
version: latest
- name: aws-ebs-csi-driver
version: latest
wellKnownPolicies:
ebsCSIController: trueBash
eksctl create cluster -f cluster.yaml
# kubeconfig
kubectl get nodesKarpenter — Intelligent Node Autoscaling
Using Karpenter instead of Cluster Autoscaler lets you reclaim unused nodes quickly and cut costs.
Installing Karpenter
Bash
export CLUSTER_NAME=prod-cluster
export AWS_REGION=ap-northeast-2
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# IRSA
eksctl create iamserviceaccount \
--name karpenter \
--namespace karpenter \
--cluster $CLUSTER_NAME \
--attach-policy-arn arn:aws:iam::$AWS_ACCOUNT_ID:policy/KarpenterControllerPolicy \
--approve
# Helm
helm upgrade --install karpenter oci://public.ecr.aws/karpenter/karpenter \
--version v0.37.0 \
--namespace karpenter --create-namespace \
--set settings.clusterName=$CLUSTER_NAME \
--set settings.interruptionQueue=$CLUSTER_NAME \
--waitDefining a NodePool and NodeClass
YAML
# karpenter-nodepool.yaml
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
nodeClassRef:
name: default
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["on-demand", "spot"]
- key: node.kubernetes.io/instance-type
operator: In
values: ["m6i.large", "m6i.xlarge", "m6a.large", "m6a.xlarge"]
- key: topology.kubernetes.io/zone
operator: In
values: ["ap-northeast-2a", "ap-northeast-2b", "ap-northeast-2c"]
limits:
cpu: 200
memory: 400Gi
disruption:
consolidationPolicy: WhenUnderutilized
consolidateAfter: 30s
---
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
name: default
spec:
amiFamily: AL2
role: KarpenterNodeRole-prod-cluster
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: prod-cluster
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: prod-cluster
blockDeviceMappings:
- deviceName: /dev/xvda
ebs:
volumeSize: 50Gi
volumeType: gp3
encrypted: trueBash
kubectl apply -f karpenter-nodepool.yamlALB Ingress Controller and HTTPS Setup
Bash
# AWS Load Balancer Controller
helm repo add eks https://aws.github.io/eks-charts && helm repo update
helm upgrade --install aws-load-balancer-controller eks/aws-load-balancer-controller \
-n kube-system \
--set clusterName=$CLUSTER_NAME \
--set serviceAccount.create=false \
--set serviceAccount.name=aws-load-balancer-controllerYAML
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-2:ACCOUNT:certificate/CERT-ID
alb.ingress.kubernetes.io/ssl-redirect: "443"
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
alb.ingress.kubernetes.io/healthcheck-path: /healthz
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80HPA — CPU- and Memory-Based Pod Autoscaling
YAML
# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 70
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # 5 after
policies:
- type: Pods
value: 2
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 30Operations — Essential Commands
Bash
#
kubectl top nodes
kubectl top pods -A
# Karpenter
kubectl get nodeclaims
kubectl get nodepools
# HPA
kubectl get hpa -w
#
kubectl set image deployment/api app=my-image:v2
kubectl rollout status deployment/api
#
kubectl rollout undo deployment/api
kubectl rollout history deployment/api --revision=3
# quit without
kubectl drain node-id --ignore-daemonsets --delete-emptydir-data
# : Spot when
kubectl taint nodes NODE_NAME karpenter.sh/disruption:NoSchedule-Hardening Security — IRSA and NetworkPolicy
Bash
# IRSA: Pod IAM
eksctl create iamserviceaccount \
--name s3-reader \
--namespace production \
--cluster $CLUSTER_NAME \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
--approveYAML
# networkpolicy.yaml — after
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes: [Ingress, Egress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-ingress
namespace: production
spec:
podSelector:
matchLabels:
app: api
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- port: 8080Editorial note
AI tools may assist drafting. Check the evidence in each guide for whether commands were run and which environment was used. Confirm the current docs for your version before applying instructions, and email us about errors. email us
Topic-related official docs (auto-linked · not per-claim sources)Kubernetes documentation ↗AWS documentation ↗
Questions & answers
Ask a question about this guide. We'll review and reply.