Finally, you have your very first k8s cluster with 2 nodes up and running well. Now let’s play with it!

  • (Optional) Install Kubernetes Dashboard

Kubernetes Dashboard is a very cool web-based UI for managing your k8s cluster.

On Master kube, install the dashboard

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta6/aio/deploy/recommended.yaml

To access this dashboard, you might need to use proxy

$ kubectl proxy

After that, you can access it by open http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#/overview?namespace=default

To login, you might need to create an user. However, for this Lab, we can just grab the namespace-controller token to login

$ kubectl -n kube-system describe secret

You will find the namespace-controller token from the output. Copy & Paste it into the dashboard authentication screen, and you will be authenticated.

  • Build and push application image into Docker registry

Go back to our repository, now we’d like to build it again, with version tag. Then push it into our Docker registry. So after it, Kubernetes can pull the image and deploy it.

$ docker build . -t martinpham/kphp:v1
$ docker tag martinpham/kphp:v1 192.168.1.33:5000/martinpham/kphp:v1
$ docker push 192.168.1.33:5000/martinpham/kphp:v1

Here we are building our Docker image, with name martinpham/kphp tag v1 (You can use whatever name you want, don’t worry!). Then push it under name 192.168.1.33:5000/martinpham/kphp:v1

  • Deploy pods

Create a Configmap configuration file: config.yml

kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-config
data:
  nginx.conf: |
    events {
    }
    http {
      server {
        listen 80 default_server;
        listen [::]:80 default_server;
        
        root /var/www/html;
        index index.html index.htm index.php;
        server_name _;
        location / {
          try_files $uri $uri/ =404;
        }
        location ~ \.php$ {
          include fastcgi_params;
          fastcgi_param REQUEST_METHOD $request_method;
          fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
          fastcgi_pass 127.0.0.1:9000;
        }
      }
    }

Nothing to scare!, we’re making a shared config across our k8s cluster. You can apply it now:

$ kubectl apply -f config.yml

Now create a Deployment configuration file: deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment
  labels:
    name: deployment
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 2
  selector:
    matchLabels:
      name: templated-pod
  template:
    metadata:
      name: deployment-template
      labels:
        name: templated-pod
    spec:
      volumes:
        - name: app-files
          emptyDir: {}
    
        - name: nginx-config-volume
          configMap:
            name: nginx-config
    
      containers:
        - image: 192.168.1.33:5000/martinpham/kphp:v1
          name: app
          volumeMounts:
            - name: app-files
              mountPath: /var/www/html
          lifecycle:
            postStart:
              exec:
                command: ["/bin/sh", "-c", "cp -r /app/. /var/www/html"]
          resources:
            limits:
              cpu: 100m
            requests:
              cpu: 50m
        
    
        - image: nginx:latest
          name: nginx
          volumeMounts:
            - name: app-files
              mountPath: /var/www/html
            - name: nginx-config-volume
              mountPath: /etc/nginx/nginx.conf
              subPath: nginx.conf
          resources:
            limits:
              cpu: 100m
            requests:
              cpu: 50m

          ports:
          - containerPort: 80
          readinessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 3
            periodSeconds: 3
            successThreshold: 1

Ok, it’s a bit spaghetti-style, but again, don’t worry:

  • Line 6: We tag this deployment as name = deployment
  • Line 8: We tell k8s to have 3 replicas during deployment.
  • Line 13: During deployment, k8s must keep at least 1 Pod available, so our website will be still online, ensure zero-downtime during deployment.
  • Line 21: We’re tagging our future Pod by name = templated-pod, so k8s can find them
  • Line 24: We create a shared volume between nginx and php, which contains our application runtime
  • Line 27: We map the config created above into another volume
  • Line 32: We create a php-fpm container from our created image, and mount the shared volume into /var/www/html
  • Line 40: We copy all application runtime from the image into the shared volume, so nginx can access it also
  • Line 41: We define limits cpu for this container
  • Line 48: We create an nginx container from Docker hub’s nginx office image.
  • Line 51: We mount the shared volume as we do with the php-fpm container
  • Line 53: We mount the config mapped above into nginx config file path
  • Line 63: We expose port 80 to the Pod
  • Line 64: We define a healthcheck endpoint (HTTP GET /) to tell k8s this Pod is running well or not

Let’s apply it

$ kubectl apply -f deployment.yml
  • Expose pods with Load Balancer

After applying the Deployment above, k8s will begin to create Pods and exposing them inside k8s network. Now we’d like to expose them via a single Load balancer endpoint.

Create a service-loadbalancer.yml file

apiVersion: v1
kind: Service
metadata:
  name: service-loadbalancer
spec:
  selector:
    name: templated-pod
  ports:
    - port: 80
      targetPort: 80

  type: LoadBalancer
  externalIPs:
    - 192.168.1.33

As told above, all Pods created with the deployment will be tagged as name = templated-pod. We just need to create a Service (Line 2) with type LoadBalancer (Line 12), tell it to balance the traffic to all Pods tagged with name = templated-pod (Line 6 & 7), via the port 80 (Line 9 & 10)

Apply it:

$ kubectl apply -f service-loadbalancer.yml

After applying the Load balancer, you can start to see your application, by browsing http://192.168.1.33 (the Master kube’s IP), great!

  • Auto-scale pods with Horizontal Pod Autoscaler

Let’s tune our infrastructure by adding a Horizontal Pod Autoscaler. It will automatic scale up / down our Pods depends on CPU/RAM/… usage. Let’s say you want to add 10 minions when traffic is high (CPU > 50%, for example), and reduce to just 3 minions when traffic is low.

Create file hpa.yml

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: hpa
spec:
  maxReplicas: 10
  minReplicas: 3
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: deployment
  targetCPUUtilizationPercentage: 50

Nothing special here:

  1. Line 6: Max minions we’re willing to have
  2. Line 7: Minimum minions to serve the website
  3. Line 10-11: Select the target for scaling: we select the Deployment controller, named deployment, as created above

Apply it:

$ kubectl apply -f hpa.yml
  • Play time

Open the Kubernetes dashboard we installed above and start monitoring our k8s:

  1. Deployment: Try to make a change to the index.php file, then rebuild & push it with tag v2. Then change the deployment.yml (Line 32) to use 192.168.1.33:5000/martinpham/kphp:v2 & apply it. You will see k8s creates 2 pods with new version, while keeping 1 pod with old version & deleting 2 old pods. When finishes, the last old pod will be deleted & another new pod will be created. No downtime during rollout!
  2. Stress-test: Try to send a lot of traffic into the Load balancer endpoint:
$ ab -n 100000 -c 10000 http://192.168.1.33/

You will see k8s monitor all the pod’s cpu, when it crosses 50%, k8s will create up to 10 pods to serve the traffic. Then try to stop the test & wait some moment to see k8s kills the pods when they’re not needed anymore. Everything will be done automatically!

I’ve created a repository contains all the code we talked about here:

https://gitlab.com/martinpham/kubernetes-fun

Have fun!