Objectives
In this lab, I will:
- Scale an application with a ReplicaSet
- Apply rolling updates to an application
- Use a ConfigMap to store application configuration
- Autoscale the application using Horizontal Pod Autoscaler
Verify the environment and command line tools
# browse to the given path
$ cd CC201/labs/3_K8sScaleAndUpdate/
Build and push application image to IBM Cloud Container Registry
# export namespace to an environment variable to be used later
export MY_NAMESPACE=sn-labs-$USERNAME
# build and push the image to the IBM Cloud Container Registry
docker build -t us.icr.io/$MY_NAMESPACE/hello-world:1 .
docker push us.icr.io/$MY_NAMESPACE/hello-world:1
Deploy the application to Kubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world
spec:
selector:
matchLabels:
run: hello-world
template:
metadata:
labels:
run: hello-world
spec:
containers:
- name: hello-world
image: us.icr.io/sn-labs-x/hello-world:1
ports:
- containerPort: 8080
resources:
limits:
cpu: 2m
memory: 30Mi
requests:
cpu: 1m
memory: 10Mi
imagePullSecrets:
- name: icr
# Running image as a deployment
$ kubectl apply -f deployment.yaml
deployment.apps/hello-world created
# Listing pods until the status is "Running"
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-world-55f8cdd55-hn7v8 1/1 Running 0 17s
# to expose it to internet via a Kubernetes Service
# create a service of type ClusterIP
$ kubectl expose deployment/hello-world
service/hello-world exposed
# to make this externally accessible
$ kubectl proxy
Starting to serve on 127.0.0.1:8001
# ping the application to get a response
$ curl -L localhost:8001/api/v1/namespaces/sn-labs-$USERNAME/services/hello-world/proxy
Hello world from hello-world-55f8cdd55-hn7v8! Your app is up and running!
Scaling the application using a ReplicaSet
# to scale up Deployment
$ kubectl scale deployment hello-world --replicas=3
deployment.apps/hello-world scaled
# to make sure the three pods created instead of just one
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-world-55f8cdd55-hn7v8 1/1 Running 0 8m51s
hello-world-55f8cdd55-l2dqr 1/1 Running 0 9s
hello-world-55f8cdd55-n9rkd 1/1 Running 0 9s
# ping application multiple times to ensure load-balancing across replicas
$ for i in `seq 10`; do curl -L localhost:8001/api/v1/namespaces/sn-labs-$USERNAME/services/hello-world/proxy; done
Hello world from hello-world-55f8cdd55-l2dqr! Your app is up and running!
Hello world from hello-world-55f8cdd55-hn7v8! Your app is up and running!
Hello world from hello-world-55f8cdd55-n9rkd! Your app is up and running!
Hello world from hello-world-55f8cdd55-l2dqr! Your app is up and running!
Hello world from hello-world-55f8cdd55-l2dqr! Your app is up and running!
Hello world from hello-world-55f8cdd55-n9rkd! Your app is up and running!
Hello world from hello-world-55f8cdd55-l2dqr! Your app is up and running!
Hello world from hello-world-55f8cdd55-n9rkd! Your app is up and running!
Hello world from hello-world-55f8cdd55-l2dqr! Your app is up and running!
Hello world from hello-world-55f8cdd55-l2dqr! Your app is up and running!
# to scale down Deployment
$ kubectl scale deployment hello-world --replicas=1
deployment.apps/hello-world scaled
# check the pods to see that two are deleted or being deleted
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-world-55f8cdd55-hn7v8 1/1 Running 0 13m
hello-world-55f8cdd55-pn4vr 1/1 Terminating 0 14s
hello-world-55f8cdd55-rgvfp 1/1 Terminating 0 14s
# to ensure only one pod exists
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-world-55f8cdd55-hn7v8 1/1 Running 0 13m
Perform rolling updates
// Change "Hello world from" to "Welcome to"
var express = require('express');
var os = require('os');
var hostname = os.hostname();
var app = express();
app.get('/', function (req, res) {
res.send('Welcome from ' + hostname + '! Your app is up and running!\n');
});
app.listen(8080, function () {
console.log('Sample app is listening on port 8080.');
});
# Rebuild the version 2 and push to ICR.
$ docker build -t us.icr.io/$MY_NAMESPACE/hello-world:2 . && docker push us.icr.io/$MY_NAMESPACE/hello-world:2
# List images in Container Registry that is pushed.
$ ibmcloud cr images
Listing images...
Repository Tag Digest Namespace Created Size Security status
us.icr.io/sn-labs-x/hello-world 1 5cf5e32e9f03 sn-labs-x 30 minutes ago 28 MB -
us.icr.io/sn-labs-x/hello-world 2 2e591de8e071 sn-labs-x 1 minute ago 28 MB -
# update the deployment to use the version instead
$ kubectl set image deployment/hello-world hello-world=us.icr.io/$MY_NAMESPACE/hello-world:2
deployment.apps/hello-world image updated
# get a status of the rolling update by using the following command
$ kubectl rollout status deployment/hello-world
# get the deployment with the `wide` option to see the new tag used
$ kubectl get deployments -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
hello-world 1/1 1 1 20m hello-world us.icr.io/sn-labs-x/hello-world:2 run=hello-world
# ping the application to ensure that the new welcome message is displayed
$ curl -L localhost:8001/api/v1/namespaces/sn-labs-$USERNAME/services/hello-world/proxy
Welcome to hello-world-55df49f45b-zjjf8! Your app is up and running!
# can rollback the deployment
$ kubectl rollout undo deployment/hello-world
deployment.apps/hello-world rolled back
# get a status of the rolling update
$ kubectl rollout status deployment/hello-world
deployment "hello-world" successfully rolled out
# get the deployment with the `wide` option to see that the old tag is used
$ kubectl get deployments -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
hello-world 1/1 1 1 31m hello-world us.icr.io/sn-labs-x/hello-world:1 run=hello-world
# ping application to ensure that the earlier "Hello World...app is running"
curl -L localhost:8001/api/v1/namespaces/sn-labs-$USERNAME/services/hello-world/proxy
Hello world from hello-world-55f8cdd55-wslb7! Your app is up and running!
Using a ConfigMap to store configuration
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world
spec:
selector:
matchLabels:
run: hello-world
template:
metadata:
labels:
run: hello-world
spec:
containers:
- name: hello-world
image: us.icr.io/sn-labs-x/hello-world:3
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: app-config
imagePullSecrets:
- name: icr
// app.js
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send(process.env.MESSAGE + '\n');
});
app.listen(8080, function () {
console.log('Sample app is listening on port 8080.');
});
# create a ConfigMap that contains a new message.
$ kubectl create configmap app-config --from-literal=MESSAGE="This message came from a ConfigMap!"
configmap/app-config created
# build version 3 and push a new image
$ docker build -t us.icr.io/$MY_NAMESPACE/hello-world:3 . && docker push us.icr.io/$MY_NAMESPACE/hello-world:3
# apply the new deployment
$ kubectl apply -f deployment-configmap-env-var.yaml
deployment.apps/hello-world configured
# ping application, message should come from ConfigMap!
$ curl -L localhost:8001/api/v1/namespaces/sn-labs-$USERNAME/services/hello-world/proxy
This message came from a ConfigMap!
# change message without rebuilding the image
$ kubectl delete configmap app-config
configmap "app-config" deleted
$ kubectl create configmap app-config --from-literal=MESSAGE="This message is different, and you didn't have to rebuild the image!"
configmap/app-config created
# restart the deployment so the container restart
$ kubectl rollout restart deployment hello-world
deployment.apps/hello-world restarted
# ping application if the new message is from environment variable
$ curl -L localhost:8001/api/v1/namespaces/sn-labs-$USERNAME/services/hello-world/proxy
This message is different, and you didn't have to rebuild the image!
Autoscale the hello-world
application using Horizontal Pod Autoscaler
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world
spec:
selector:
matchLabels:
run: hello-world
template:
metadata:
labels:
run: hello-world
spec:
containers:
- name: hello-world
image: us.icr.io/sn-labs-x/hello-world:1
ports:
- containerPort: 8080
name: http
resources:
limits:
cpu: 50m
requests:
cpu: 20m
imagePullSecrets:
- name: icr
# apply the deployment
$ kubectl apply -f deployment.yaml
deployment.apps/hello-world configured
# autoscale `hello-world` deployment
$ kubectl autoscale deployment hello-world --cpu-percent=5 --min=1 --max=10
horizontalpodautoscaler.autoscaling/hello-world autoscaled
# initial observation the replicas increase in accordance with the autoscaling
$ kubectl get hpa hello-world --watch
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
hello-world Deployment/hello-world 0%/5% 1 10 1 114s
# spam the app with multiple requests for increasing the load
# in separate command
$ for i in `seq 100000`; do curl -L localhost:8001/api/v1/namespaces/sn-labs-$USERNAME/services/hello-world/proxy; done
Hello world from hello-world-6c59687774-l26sv! Your app is up and running!
Hello world from hello-world-6c59687774-7t8kd! Your app is up and running!
dial tcp 172.17.153.203:8080: connect: connection refusedHello world from hello-world-6c59687774-sd5px! Your app is up and running!
Hello world from hello-world-6c59687774-4h4zr! Your app is up and running!
Hello world from hello-world-6c59687774-hxhq8! Your app is up and running!
# observe again the replicas increase in accordance with the autoscaling
$ kubectl get hpa hello-world --watch
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
hello-world Deployment/hello-world 0%/5% 1 10 1 9m41s
hello-world Deployment/hello-world 0%/5% 1 10 1 10m
hello-world Deployment/hello-world 105%/5% 1 10 1 10m
hello-world Deployment/hello-world 105%/5% 1 10 4 11m
hello-world Deployment/hello-world 105%/5% 1 10 8 11m
hello-world Deployment/hello-world 30%/5% 1 10 10 11m
# observe the details of horizontal pod autoscaler
$ kubectl get hpa hello-world
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
hello-world Deployment/hello-world 4%/5% 1 10 10 14m
# delete the deployment
$ kubectl delete deployment hello-world
deployment.apps "hello-world" deleted
# delete the service
$ kubectl delete service hello-world
service "hello-world" deleted