Again, the intent here is an introduction of K8s. Hence, I will not be going into the details but just touching the surface and moving our existing App to K8s. So, let’s get started.
Here is what we will be covering —
- Setting up K8s using Docker Desktop or manually using hyperkit and minikube
- Moving the mysql database to K8s
- Moving the redis instance to K8s
- Moving the App to K8s
- Getting it all together by Kubernetes magic
Source code in Github: https://github.com/sameerbhatt/url-shortener-app
Setting up K8s -
Install Docker Desktop to get most of the things automatically. You can Enable Kubernetes from Docker Desktop → Settings → Kubernetes
brew install hyperkit
brew install minikube// Creating a minikube cluster
minikube start --vm-driver=hyperkit
Now, you might not be able to use your local images from Docker Desktop. See details here on how to fix -https://github.com/kubernetes/minikube/blob/0c616a6b42b28a1aab8397f5a9061f8ebbd9f3d9/README.md#reusing-the-docker-daemon
For this example, we will continue to use Docker Desktop. For this, you have to set the correct context now —
// Check the context
kubectl config current-context// Set context as docker desktop
kubectl config use-context docker-desktop// check version to see you get both Client and Server Versions
Let’s move to the next step. We will try to minimize the changes to our existing apps so that there is no change when used directly with docker or with Kubernetes.
Moving Mysql database to K8s
Create a directory kube-config in the url-shortener-app and create a mysql.yaml file as follows -
- image: mysql:8
- name: MYSQL_ROOT_PASSWORD
- containerPort: 3306
- name: mysql-volume
- name: mysql-volume
# directory location on host
- port: 3306
Let’s analyze it now -
- apiVersion, kind, metadata and other common fields, read here— https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/ https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
- Image related —
image: mysql:8 — image name and tag
imagePullPolicy: Never — this is important since our image is available locally, we don’t want to fetch it from the global docker registry for now. There are various options to use here based on your requirements
- Volume — Similar to how we used a local host directory for storing all mysql data for persistence in Docker, we will need the same path so that our existing data remains. hostPath is used here as I wanted to continue from our previous example. You can use persistentVolumeClaim as well. https://kubernetes.io/docs/concepts/storage/volumes/
- Service — you now need to expose the mysql database as a network service. https://kubernetes.io/docs/concepts/services-networking/service/
The specification shown above creates a new Service object named “mysql-url-shortener”, which targets port 3306 on any Pod with the app=mysql-url-shortener label.
Note: There is no service type here, which mean it will default to ClusterIP. ClusterIP: Exposes the Service on a cluster-internal IP. Choosing this value makes the Service only reachable from within the cluster. For our Node App, we will be using a different type. Stay tuned to read more!
Setting up Secrets If you are wondering, we missed to discuss about secretKeyRef, don’t worry, we will go over it now. Here is the snippet that I’m talking about in mysql.yaml file-
Basically, Kubernetes Secrets let you store and manage sensitive information, such as passwords, OAuth tokens, and ssh keys.
So, we will get the base64 value of our mysql password and use it as the value of the mysql-password field in the secrets file (see below). And then refer the secret in our mysql yaml file (as shown above).
echo -n 'root123' | base64
// outputs cm9vdDEyMw==
Here is the content of the secrets.yaml file —
Moving Redis to K8s
This is mostly similar to the mysql setup above. So, in the directory kube-config in the url-shortener-app, create a redis.yaml file as follows -
- name: redis-url-shortener
- containerPort: 6379
- port: 6379
If you notice, most of the things are same as mysql yaml file, except the name, image and ports. So, let’s not spend more time on it.
Moving the App to K8s
This is also similar to the above two setups, except a few fields that we will cover. So, in the directory kube-config in the url-shortener-app, create a app.yaml file as follows -
- name: url-shortener-app
- containerPort: 3000
- port: 3000
Fields to notice here -
- replicas: 1 — here, you can specify the number of replicas you want for your app depending on the expected traffic. Default is 1.
- image: url-shortener-app:1.0 — This is our App image that we built using docker.
- Service type: LoadBalancer — This is an important field. Since, we want to expose our service externally, we have changed to type LoadBalancer. From the documentation -
LoadBalancer: Exposes the Service externally using a cloud provider’s load balancer. NodePort and ClusterIP Services, to which the external load balancer routes, are automatically created.
Getting everything together
It’s time for some Kubernetes magic now. Make sure your kube-config directory have the following files — app.yaml, mysql.yaml, redis.yaml and secrets.yaml
$ kubectl apply -f kube-config
Once, this is done, you can see the status as follows -
$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/mysql-url-shortener-bcb64d6fd-hcgfd 1/1 Running 0 65s
pod/redis-url-shortener-9768b8985-9qzbv 1/1 Running 0 65s
pod/url-shortener-deployment-77bd88587d-525b2 1/1 Running 0 65sNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d21h
service/mysql-url-shortener ClusterIP 10.107.253.197 <none> 3306/TCP 65s
service/redis-url-shortener ClusterIP 10.109.49.50 <none> 6379/TCP 65s
service/url-shortener-app LoadBalancer 10.105.241.67 localhost 3000:30059/TCP 65sNAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/mysql-url-shortener 1/1 1 1 65s
deployment.apps/redis-url-shortener 1/1 1 1 65s
deployment.apps/url-shortener-deployment 1/1 1 1 65sNAME DESIRED CURRENT READY AGE
replicaset.apps/mysql-url-shortener-bcb64d6fd 1 1 1 65s
replicaset.apps/redis-url-shortener-9768b8985 1 1 1 65s
replicaset.apps/url-shortener-deployment-77bd88587d 1 1 1 65s
The above is mostly self-explanatory. There are pods, services, deployments and replicasets created.
Test it out now by opening http://localhost:3000/ in the browser.
Important: At times, you might notice the following status for the App Pod (notice the RESTARTS column) -
NAME READY STATUS RESTARTS AGE
pod/url-shortener-deployment-77bd88587d-r7xbp 1/1 Running 2 35s
What this means is that there was some error and the Pod crashed and then restarted twice before being ready. Give it some time to think over why it’s happening and then read further 🙂
The error lies in our code in db.js
We are trying to connect to the database only once and failing on error. What if, the mysql Pod isn’t ready by then. Our code will continue to crash till then. Kubernetes comes to the rescue by starting the Pod again and in this case, after 2 re-tries, it connects as the mysql Pod is up. That said, we should fix this in our code to keep trying for sometime to connect to the mysql database instead of trying once and failing.
To bring the Cluster down -
$ kubectl delete -f kube-config
deployment.apps "url-shortener-deployment" deleted
service "url-shortener-app" deleted
deployment.apps "mysql-url-shortener" deleted
service "mysql-url-shortener" deleted
deployment.apps "redis-url-shortener" deleted
service "redis-url-shortener" deleted
secret "mysecrets" deleted
Also, you might have noticed that there is no changes required in our original code-base. Everything just works. We were able to refer to the mysql and redis instances by the same names because we used same names in the mysql.yaml and redis.yaml files.
Here are some useful Kubernetes commands —
// minikube status
minikube status// status
kubectl get all/ nodes/ pod/ service/ replicaset// Edit a deployment, you don't need to apply/ delete/ create anything. It's managed automatically by K8s
kubectl edit deployment NAME// show logs for a Pod, add -f to stream the logs
kubectl logs POD_NAME// delete deployment
kubectl delete deployment NAME// open terminal/shell of the Pod. it = iterative terminal
kubectl exec -it POD_NAME -- bash// more details for a pod including IP address
kubectl get pod -o wide// scale up/down a deployment, change value of replicas
kubectl scale deployment url-shortener-deployment --replicas=1;// details of all services
kubectl get svc// details for a service
kubectl describe service NAME// details of the stored secrets
kubectl get secret mysecrets -o yaml
And finally — https://kubernetes.io/docs/reference/kubectl/cheatsheet/
That’s all for now. I hope, this intro helped you get started with Kubernetes :-)