Hello there! This blog post will detail how to use HorizontalPodAutoscaler
to horizontally scale a pod within kubernetes, for this demonstration I am going to be using minikube installed on my local macbook, therefore as a prerequisite please ensure you have docker and minikube setup and functioning as expected (I may come back to how to do this in a future blog post but otherwise google is your friend!)
Preparation
Firstly make sure you have minikube installed:
minikube version
minikube version: v1.25.2
commit: 362d5fdc0a3dbee389b3d3f1034e8023e72bd3a7
Start your minikube instance:
minikube start
😄 minikube v1.25.2 on Darwin 12.4 (arm64)
✨ Automatically selected the docker driver
👍 Starting control plane node minikube in cluster minikube
🚜 Pulling base image ...
🔥 Creating docker container (CPUs=2, Memory=2200MB) ...
🐳 Preparing Kubernetes v1.23.3 on Docker 20.10.12 ...
▪ kubelet.housekeeping-interval=5m
▪ Generating certificates and keys ...
▪ Booting up control plane ...
▪ Configuring RBAC rules ...
🔎 Verifying Kubernetes components...
▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟 Enabled addons: storage-provisioner, default-storageclass
🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
Check kubectl is working properly:
kubectl get pods
No resources found in default namespace.
kubectl get nodes
NAME STATUS ROLES AGE VERSION
minikube Ready control-plane,master 40s v1.23.3
Enable metrics server for minikube:
minikube addons enable metrics-server
▪ Using image k8s.gcr.io/metrics-server/metrics-server:v0.4.2
🌟 The 'metrics-server' addon is enabled
Details on how to do this outside of minikube can be found here: https://github.com/kubernetes-sigs/metrics-server#readme
NOTE If like me you have difficulties accessing the minikube instance on port 80 or any port for that matter, it may be you’re using a m1 device, I had to start minikube with this:
minikube start --driver=docker --ports=127.0.0.1:30080:30080 --disable-optimizations
FYI I had to use –disable-optimizations due to this: https://github.com/kubernetes/minikube/issues/13898
Check the ports have been forwarded here:
docker port minikube
22/tcp -> 127.0.0.1:50820
2376/tcp -> 127.0.0.1:50821
30080/tcp -> 127.0.0.1:30080
32443/tcp -> 127.0.0.1:50817
5000/tcp -> 127.0.0.1:50818
8443/tcp -> 127.0.0.1:50819
Run and expose the php container
First lets create the directory to hold our terraform code with a mkdir
mkdir kubeautoscale-$(date +"%Y%m%d%H%M")
Create the below file within your directory php-apache.yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-apache
spec:
selector:
matchLabels:
run: php-apache
replicas: 1
template:
metadata:
labels:
run: php-apache
spec:
containers:
- name: php-apache
image: k8s.gcr.io/hpa-example
ports:
- containerPort: 80
resources:
limits:
memory: 100Mi
cpu: 100m
requests:
memory: 100Mi
cpu: 100m
---
apiVersion: v1
kind: Service
metadata:
name: php-apache
labels:
run: php-apache
spec:
type: NodePort
selector:
run: php-apache
ports:
- protocol: TCP
port: 80
nodePort: 30080
Okay that looks good to me how’s it looking to you? Good? Great let’s apply this sucker.
kubectl apply -f php-apache.yaml
deployment.apps/php-apache created
service/php-apache created
Give it a moment it’s probably still creating…you can check with…
kubectl get pod php-apache-7656945b6b-trg6r -w
-w
means watch, so you know it watches!
Eventually you should see your pod alive and well:
NAME READY STATUS RESTARTS AGE
php-apache-7656945b6b-trg6r 1/1 Running 0 62s
Check the deployment:
kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
php-apache 1/1 1 1 105s
Check the service:
kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2m19s
php-apache NodePort 10.101.86.82 <none> 80:30080/TCP 75s
So far so good, lets curl the service make sure its responding:
curl http://127.0.0.1:30080
OK!%
Awesome it’s responding!!!
Scaling time
Now it’s time to scale like spiderman up the side of a building in New York when fighting..you know what just tell me to stop!
We can create the autoscaler using kubectl. There is kubectl autoscale subcommand, part of kubectl which we’re going to utilise here!
Our intention is to create a HorizontalPodAutoscaler that maintains between 1 and 10 replicas of the Pods controlled by the php-apache Deployment that we created in the first step of these instructions.
HPA controller will increase and decrease the number of replicas (by updating the Deployment) to maintain an average CPU utilization across all Pods of 50%. The Deployment then updates the ReplicaSet - this is part of how all Deployments work in Kubernetes - and then the ReplicaSet either adds or removes Pods based on the change to its .spec.
The below command takes care of the autoscale deployment:
kubectl autoscale deployment php-apache --cpu-percent=20 --min=1 --max=10
Lets check the status of it:
kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache <unknown>/20% 1 10 0 14s
If you see <unknown>
you may want to wait 5 minutes when I did I got:
kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache 0%/20% 1 10 1 2m45s
You can also check the issue if it doesn’t resolve by doing kubectl describe hpa
and getting your google on!!
Time to increase the load
Time to give this pod a heart attack!
Note Run this next part in a seperate terminal!
Open a new terminal window and run the below, what we’re doing here is creating a different Pod to act as a client to our php-apache pod. The container within the client Pod runs in an infinite loop, sending queries to the php-apache service.
kubectl run -i --tty load-generator --rm --image=busybox:1.28 --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://php-apache; done"
You should see the output now:
If you don't see a command prompt, try pressing enter.
OK!OK!OK!OK!
Its going to start hitting it hard and fast 😎
In your other console run: kubectl get hpa php-apache --watch
You should start to see the CPU ramp up
You can check with:
kubectl top pod
NAME CPU(cores) MEMORY(bytes)
load-generator-2 0m 0Mi
php-apache-ff74b65f6-7ng2k 0m 3Mi
Within <3 minutes I was at 29% already:
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache 29%/20% 1 10 1 3m38s
1 minute later you’ll see the replicas are growing:
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache 26%/20% 1 10 8 5m44s
As a result, the Deployment was resized to 8 replicas:
NAME READY UP-TO-DATE AVAILABLE AGE
php-apache 8/8 8 8 7m33s
Note: It may take a few minutes to stabilize the number of replicas. Since the amount of load is not controlled in any way it may happen that the final number of replicas will differ from this example.
Time to reduce the load
In the terminal where your busybox container is running (the one hammering php) crtl+c the console:
Wait a few minutes (I waited 10 minutes) for the load to drop:
kubectl get hpa php-apache --watch ✔ at minikube ⎈ at 14:58:10
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache 1%/20% 1 10 1 15m
Notice replicas is back down to 1
kubectl get deployment php-apache
NAME READY UP-TO-DATE AVAILABLE AGE
php-apache 1/1 1 1 18m
One final thing I found that is pretty cool is that you can export your HPA configuration into YAML to commit to source:
kubectl get hpa php-apache -o yaml > hpa.yaml
(Note I haven’t used the above yet so if that doesn’t work then pfft!)
Conclusion
So this post ran you through some of the basics of running a scalable php you can take this further and use other metrics to scale see here
Credits
Big shout out to the below articles for guiding me through this one: