[Backend #32] How to deploy a web app to Kubernetes cluster on AWS EKS

[Backend #32] How to deploy a web app to Kubernetes cluster on AWS EKS


[Backend #32] How to deploy a web app to Kubernetes cluster on AWS EKS

In this lecture, we will learn how to deploy a web application container to a Kubernetes cluster on AWS EKS.
- Join us on Discord: https://bit.ly/techschooldc
- Get the course on Udemy: https://bit.ly/backendudemy
- Buy us a coffee: https://www.buymeacoffee.com/techschool
- Full series playlist: https://bit.ly/backendmaster
- Github repository: https://github.com/techschool/simplebank
---

In this backend master class, we’re going to learn everything about how to design, develop, and deploy a complete backend system from scratch using PostgreSQL, Golang, and Docker.

TECH SCHOOL - From noob to pro
   / techschoolguru  
At Tech School, we believe that everyone deserves a good and free education. We create high-quality courses and tutorials in Information Technology. If you like the videos, please feel free to share and subscribe to support the channel or buy us a coffee: https://www.buymeacoffee.com/techschool


Content

0.089 -> Hello everyone, welcome to the backend master class.
3.5 -> In the previous lectures, we’ve learned how to create an EKS cluster on AWS
8.64 -> And connect to it using kubectl or k9s.
11.19 -> Today let’s learn how to deploy our simple bank API service to this Kubernetes cluster.
17.29 -> So basically, we have built a docker image for this service and push it to Amazon ECR,
22.92 -> And now we want to run this image as a container in the Kubernetes cluster.
27.67 -> In order to do so, we will need to create a deployment.
31.84 -> Deployment is simply a description of how we want our image to be deployed.
36.37 -> You can read more about it on the official Kubernetes documentation page.
40.219 -> And here’s an example of a typical Kubernetes deployment.
44.76 -> So let’s copy its content,
47.149 -> Open our simple bank project.
49.34 -> Then in the eks folder, I’m gonna create a new file called deployment.yaml
54.68 -> And paste in the content of the sample deployment.
57.32 -> On the first line is the version of the Kubernetes API
60.77 -> we’re using to create this deployment object.
63.43 -> Then on the second line, is the kind of object we want to create,
68.039 -> which is deployment in this case.
70.249 -> Next, there’s a metadata section, where we can specify some metadata for the object.
75.909 -> For example, the name of the object, I’m gonna call it simple-bank-api-deployment.
81.09 -> And the labels are basically some key-value pairs that are attached to the object
87.469 -> Which are useful for the users to easily organize and select subsets of objects.
93.889 -> Here I’m gonna add only 1 label for the app, which is called simple-bank-api.
99.499 -> Now comes the main specification of the deployment object.
102.889 -> First, we can set the number of replicas,
105.289 -> or the number of pods we want to run with the same template.
108.829 -> For now, let’s run just 1 single pod.
112.09 -> Next, we need to specify a pod selector for this deployment.
116.079 -> It’s basically a rule that defines how the deployment can find which pods to manage.
121.939 -> In this case, we’re gonna use a matchLabels rule.
126.059 -> And I’m gonna use the same label app: simple-bank-api as we’ve used before.
130.89 -> This means that all pods that have this label will be managed by this deployment.
135.92 -> Therefore, in the next section, pod template, we must add the same label to its metadata.
142.129 -> Alright, now comes the spec of the pod.
144.959 -> This is where we tell the deployment how to deploy our containers.
149.39 -> First, the name of the container is gonna be simple-bank-api
153.879 -> Then the URL to pull the image from.
156.629 -> As our simple-bank images are stored in Amazon ECR,
160.29 -> let’s open it in the browser to get the URL.
163.94 -> Here we can see, there are several images with different tags.
167.93 -> I’m gonna select the latest one,
170.68 -> And copy its image URL by clicking on this button.
174.409 -> Then paste it to our deployment.yaml file.
177.829 -> Note that this long suffix of the URL is the tag of the image.
182.48 -> And it’s basically the git commit hash as we’ve set up in one of the previous lectures.
187.719 -> For now, we’re setting this tag value manually,
190.099 -> But don’t worry, in later lectures,
192.829 -> I will show you how to change it automatically with the Github Actions CI/CD.
197.64 -> Alright, now the last thing we’re gonna do is to specify the container port.
202.37 -> This is the port that the container will expose to the network.
207.22 -> Although it is totally optional, it’s still a good practice to specify this parameter
212.51 -> because it will help you or other people to understand better the deployment configuration.
217.29 -> OK, I think that should be it.
220.769 -> The deployment file is completed.
223.7 -> But before we apply it, let’s use k9s to check out the current state of the EKS cluster
229.349 -> That we have set up on AWS in previous lectures.
233.23 -> If we select the default namespace,
235.37 -> We can see that there are no running pods at the moment.
239.739 -> And the deployments list is also empty.
243.319 -> Now let’s apply the deployment file that we’ve written before
247.109 -> Using the kubectl apply command.
249.92 -> We use the -f option to specify the location of the object we want to apply,
253.999 -> Which, in this case, is the deployment.yaml file inside the eks folder.
260.109 -> OK, it’s successful, and the simple-bank-api deployment has been created.
265.3 -> Let’s check it out in the k9s console.
269.139 -> Here it is, the deployment has shown up in the list,
272.25 -> but somehow it is not ready yet.
275.86 -> To see more details, we can press d to describe this deployment object.
280.78 -> OK so the image URL is correct.
283.65 -> And in the events list, it says scaled up replica set simple-bank-api-deployment to
289.94 -> 1.
290.96 -> All looks pretty normal.
292.199 -> But why this deployment is not ready yet?
295.199 -> Let’s press enter to open the list of pods that this deployment manages.
300.009 -> OK so looks like the pod is not ready yet.
304.06 -> Its status is still pending.
306.05 -> Let’s describe it to see more details.
309.139 -> If we scroll down to the bottom to see the events list,
312.5 -> We can see that there’s a warning event: failed scheduling.
316.789 -> And that’s because there are no nodes available to schedule pods.
320.88 -> Alright, now we know the reason,
323.099 -> Let’s go to the AWS console page,
326.229 -> and open the EKS cluster simple-bank that we’ve set up in previous lectures.
330.41 -> Voilà, here it says “This cluster does not have any attached nodes”.
335.78 -> Let’s open the configuration tab, and select compute section.
340.25 -> In the Node Groups table, we can see that the desired size is 0,
345.419 -> So that’s why it didn’t create any nodes (or EC2 instances).
349.849 -> To fix this, let’s open the simple-bank node group,
353.199 -> And follow this link to open the auto-scaling group.
356.509 -> Here we can see, its desired capacity is 0, and so is the minimum capacity.
362.129 -> Let’s click this Edit button to change it.
365.19 -> I’m gonna increase the desired capacity to 1.
369.12 -> Note that this number must be within the limit range of the minimum and maximum capacity.
374.16 -> OK, let’s click Update.
377.29 -> Now the desired capacity has been changed to 1.
380.33 -> And in the Activity tab, if we refresh the activity history,
385.62 -> We can see a new entry saying launching a new EC2 instance.
389.169 -> This might take a while to complete.
392.02 -> So let’s refresh the list.
395.11 -> Now its status has changed to Mid Lifecycle Action
398.05 -> Let’s wait a bit, and refresh again.
402.72 -> This time, the status is Successful.
405.539 -> So now we have 1 instance available in the node group.
408.26 -> Let’s go back to the EKS cluster’s node group page.
412.28 -> Select the nodes tab, and click this refresh button.
416.47 -> This time, there’s 1 node in the group.
417.81 -> But its status is still not ready.
420.409 -> We have to wait a bit for the EC2 instance to be set up.
424.22 -> Alright, now the node is ready.
426.199 -> Let’s go back to the k9s console to see what happens to the pods.
430.349 -> OK, it’s still in pending state.
433.569 -> Let’s find out why!
436.15 -> Now at the bottom, there are 2 new events.
439.11 -> The first one says 0/1 nodes are available, 1 node had taint: not ready,
444.75 -> that the pod didn’t tolerate.
447.21 -> And the second one says: Too many pods.
449.95 -> So this means that the cluster has recognized the new node,
453.289 -> And the deployment tried to deploy a new pod to this node,
456.439 -> But somehow the node already has too many pods running on it.
460.22 -> Let’s dig deeper to find out why.
462.68 -> I’m gonna open the list of nodes.
465.449 -> This is the only node of the cluster.
467.75 -> Let’s describe it!
469.78 -> If we scroll down a bit to the capacity section,
472.78 -> We can see some hardware configurations of the node, such as the CPU or memory.
478.58 -> And at the bottom is the maximum number of pods can run on this node, which is 4 in our
484.449 -> case.
485.449 -> There’s also a section to tell you the size of the resources that can be allocated to
489.18 -> this node.
490.78 -> And if we scroll down a little bit more,
492.819 -> We can see the number of non-terminated pods.
495.3 -> Currently, there are 4 running pods.
498.58 -> And they are listed in this table.
500.37 -> All 4 pods belong to the kube-system namespace.
504.62 -> so these 4 kubernetes system pods
506.919 -> has already taken up all 4 available pod slots of the node.
510.759 -> That’s why the deployment cannot create a new one for our container.
514.96 -> In case you don’t know,
516.47 -> the maximum number of pods can run on an EC2 instance
520.27 -> depends on the number of Elastic Network Interfaces (or ENI)
524.81 -> and the number of IPs per ENI allowed on that instance.
529.05 -> This github page of Amazon gives us a formula
531.65 -> to compute the maximum number of pods based on those numbers.
535.31 -> It is: number of ENI multiplied by (number of IPs per ENI - 1) then plus 2.
542.63 -> There is also a documentation page of Amazon
545.66 -> that gives us the number of ENIs and IPs per ENI for each instance type.
550.1 -> If you still remember, we’re using a t3.micro instance for our node group,
555.94 -> So according to this table, it has 2 ENI, and 2 IPs per ENI.
562.19 -> Now if we put these numbers into the formula,
564.49 -> we will get 2 * (2-1) + 2 = 4,
569.91 -> which is the maximum number of pods that can run on this type of instance.
573.48 -> If you’re lazy to do the math, you can just search for t3.micro on this page,
579.6 -> Then here you are, 4 is the maximum number of pods.
582.61 -> OK, so in order to run at least 1 more pod on the node, we will need a bigger instance
587.65 -> type
588.65 -> T3.nano is also not enough resource to run more than 4 pods,
593.17 -> But a t3.small instance can run up to 11 pods,
597.19 -> so it should be more than enough for our app.
599.84 -> Alright, now we need to go back to the Amazon EKS cluster node group page.
604.98 -> As you can see here, the current instance type of this node group is t3.micro
609.6 -> Let’s try to change it to t3.small
612.84 -> In this edit node group page, we can change several things,
616.03 -> such as the scaling, the labels, taints, tags, or update configuration.
621.05 -> But there’s no option to change the instance type of the node group.
624.03 -> So I guess we’re gonna need to delete this node group and create a new one.
628.34 -> Let’s do that!
630.05 -> To delete this node group, we have to enter its name here to confirm
634.08 -> Then click this Delete button.
636.06 -> OK, now if we go back to the cluster page,
639.25 -> We can see the status of the node group has changed to Deleting.
641.97 -> After a few minutes, we can refresh the page.
646.53 -> Now the old node group has gone.
648.41 -> Let’s click Add Node Group button to create a new one.
651.35 -> I’m gonna use the same name: simple-bank for this node group.
656.01 -> Select the AWS EKS Node Role that we’ve created in the previous lectures.
660.91 -> Then scroll all the way down, and click Next.
663.96 -> For the node group configuration,
665.86 -> We will use the default values:
667.43 -> Amazon Linux 2 for the image type,
670.16 -> and On-demand for the capacity type.
673.1 -> But for the instance type, we will choose t3.small instead of t3.micro as before.
679.61 -> Here we can see the max ENI is 3, and max IP is 12.
684.21 -> So looks like the maximum number of pods is equal to this max number of IPs minus 1
689.84 -> OK, next, for the disk size, let’s set it to 10 GiB.
694.85 -> Then for the node group scaling,
696.25 -> I’m gonna set the minimum size to 0,
698.78 -> and the desired size to 1 node.
701.63 -> Then let’s move to the next step.
704.13 -> Here the default subnets are already selected,
706.85 -> so I’m just gonna use them.
708.43 -> No need to change anything.
710.32 -> In the last step, we can review all the configurations of the node group.
713.93 -> And if they all look good, we can go ahead to create the group.
718.12 -> OK, so the group is now being created.
721.18 -> This might take a while to complete.
723.63 -> So while waiting for it, let’s go back to the k9s console
727.51 -> and delete the existing deployment.
729.95 -> To do that, we simply press ctrl + d.
733.42 -> Then select OK, enter
735.42 -> And that’s it, the deployment is deleted.
738.64 -> And its managed pod is deleted as well.
741.81 -> Alright, so now the cluster is back to a clean state, ready for a new deployment.
747.12 -> Now, let’s refresh the page to see if the node group is ready or not.
751.25 -> OK, its status is now Active, so it should be ready.
755.46 -> Let’s open the terminal, and run the “kubectl apply” command to deploy our app.
760.81 -> The deployment is created.
762.35 -> Let’s check it out in the k9s console.
764.71 -> Yay, I think it works, because this time the color has just changed from red to green,
770.35 -> And it says READY 1/1 here.
773.42 -> Let’s describe it to see more details.
775.48 -> OK, everything looks good.
777.03 -> The number of replicas is 1.
780.22 -> Let’s check out the pods.
781.88 -> There’s 1 pod, and its status is Running.
785.08 -> Perfect!
786.08 -> Let’s describe this pod.
788.58 -> Scroll all the way to the bottom.
790.57 -> We can see several normal events.
792.37 -> And they’re all successful.
794.55 -> The container has been created and started with no errors.
798.66 -> Now if we go back to the pods list, and press enter,
802.2 -> It will bring us to the list of containers.
804.42 -> So, there’s only 1 single container running in the pod.
808.22 -> If we want to see the logs of this container,
810.52 -> we can simply press L, as it’s clearly written here.
814.25 -> OK, here are the logs.
816.7 -> It first ran the db migration,
819.12 -> Then the app was successfully started.
820.68 -> The server is now listening and serving HTTP requests on port 8080.
825.8 -> That’s great!
827.52 -> But the next question is: how can we send requests to this API?
831.36 -> If we go back to the pod list, we can see the IP address of the pod,
835.63 -> However, it’s just an internal IP, and cannot be accessed from outside of the cluster.
841.86 -> In order to route traffic from the outside world to the pod,
845.48 -> we need to deploy another Kubernetes object, which is a Service.
850.31 -> You can read more about it on the official Kubernetes documentation page.
854.56 -> Basically, a service is an abstraction object
858.08 -> that defines a set of rules to route network traffics
861.02 -> to the correct application running on a set of pods.
864.44 -> Load balancing between them will be handled automatically,
868.29 -> Since all the pods of the same deployment will share a single internal DNS.
872.83 -> OK, here’s an example of how we can define a service.
877.32 -> I’m gonna copy it.
879.29 -> Then go back to the code,
880.84 -> Let’s create a new file: service.yaml inside the eks folder.
885.99 -> Then paste in the content of the example service.
889.38 -> It also starts with the api version, just like the Deployment object.
893.9 -> But now, the kind of this object is Service.
897.09 -> We also have a metadata section to store some information about this service.
901.05 -> Here I’m just gonna specify the name, which is simple-bank-api-service.
904.43 -> Next, the specification of the service.
908.66 -> First, we must define a pod selector rule,
911.42 -> so that the service can find the set of pods to route the traffic to.
915.6 -> We’re gonna use a label selector,
918.52 -> So I’m gonna copy the app label from the pod template in deployment.yaml file
924.01 -> And paste it to the service.yaml file here, under the selector section.
928.62 -> OK, next we have to specify the rule for ports.
932.01 -> This service will listen to HTTP API requests, so the protocol is TCP
937.67 -> Then, 80 is the port, on which the service will listen to incoming requests.
943.43 -> And finally, the target port is the port of the container,
946.73 -> Where the requests will be sent to.
948.44 -> In our case, the container port is 8080, as we’ve specified in the deployment file.
954.84 -> So I’m gonna change this target port value to 8080 in the service file.
959.73 -> And that’s it!
960.78 -> The service.yaml file is done.
963.33 -> Now let’s open the terminal
965.17 -> and run kubectl apply -f eks/service.yaml to deploy it.
971.56 -> OK, the service is created.
973.99 -> Let’s check it out in the k9s console.
976.63 -> I’m gonna search for services.
979.71 -> Here we go.
980.71 -> In the list of services, beside the system service of kubernetes,
984.97 -> we can see our simple-bank api service.
986.64 -> Its type is ClusterIP, and here’s its internal cluster IP.
993.22 -> And it’s listening on port 80 as we’ve specified in the yaml file.
998.09 -> But look at the external IP column!
1000.44 -> It’s empty!
1001.63 -> Which means this service doesn’t have an external IP.
1004.98 -> So how can we access it from outside?
1007.38 -> Well, in order to expose the service to the outside world,
1011.28 -> we need to change its type.
1013.05 -> By default, if we don’t specify anything, the service’s type will be ClusterIP.
1017.88 -> Now, let’s change its type to LoadBalancer instead.
1021.8 -> Then save the file, and go back to the terminal to apply it again.
1026.64 -> OK, the service is configured.
1028.909 -> This time, in the k9s console, we can see that its type has changed to LoadBalancer,
1035.089 -> And there’s an external IP, or a domain name has been assigned to the service.
1039.47 -> Awesome!
1040.47 -> But to make sure that it’s working well,
1042.689 -> Let’s try nslookup this domain.
1044.63 -> Oops, we’ve got an error: server can’t find this domain name.
1049 -> Maybe it will take a bit of time for the domain to be ready.
1054.75 -> Now let’s try nslookup again.
1056.27 -> This time, it’s successful.
1057.5 -> You can see that there are 2 IP addresses associated with this domain.
1061.49 -> That’s because it’s a network load balancer of AWS.
1065.059 -> OK, now let’s try sending some requests to the server!
1068.6 -> I’m gonna open Postman, and try the login user API.
1072.08 -> We must replace the URL localhost:8080 with the production domain name of the service.
1078.759 -> And if I remember correctly,
1080.23 -> we’ve already created user Alice in the production DB in one of the previous lectures.
1085.03 -> I’m gonna check it quickly with TablePlus.
1087.52 -> Yes, that’s right!
1089.889 -> User Alice already existed.
1091.419 -> So let’s go back to Postman and send the request.
1095.179 -> Yee!
1096.179 -> It’s successful.
1097.179 -> We’ve got an access token together with all user information.
1101.2 -> So it worked!
1102.549 -> Now let’s see the logs of the container.
1104.919 -> Here we go: a POST request to /users/login
1108.1 -> Alright, now if we go back to the service and describe it,
1112.559 -> We can see that it’s sending the request to only 1 single endpoint.
1116.279 -> That’s because right now we only have only 1 single replica of the app.
1120.46 -> Let’s see what will happen if we change the number of replicas to 2 in the deployment.yaml
1125.429 -> file
1126.429 -> Save it, and run kubectl apply in the terminal to redeploy to the cluster.
1132.149 -> OK, now, let’s go to the deployments list.
1135.95 -> This time, we can see READY 2/2,
1139.74 -> which means there are 2 replicas, or 2 pods up and running.
1144.009 -> Here they are!
1145.009 -> Now let’s take a look at the service.
1147.1 -> If we describe the simple-bank API service,
1150.35 -> We can see that it is now forwarding requests to 2 different endpoints,
1154.98 -> And those endpoints are actually the address of the 2 pods where our application is running
1159.5 -> on.
1160.5 -> Alright, let’s go back to Postman and send the request again to make sure it still works.
1166.41 -> Cool!
1167.41 -> The request is successful.
1168.41 -> Even when I send it multiple times.
1170.799 -> So the service is handling well the load balancing of the request when there are multiple pods.
1176.46 -> Now before we finish, let’s check out the resources of the node.
1179.72 -> I’m gonna describe this node,
1182.49 -> And scroll down to the capacity section.
1185.299 -> Here we can see that the maximum number of pods that can run on this node is 11,
1190.129 -> Exactly as we calculated for a t3.small instance.
1194.179 -> And if we scroll down a little bit more,
1196.7 -> We can see that at the moment, there are 6 pods running on this node.
1201.21 -> 4 of them are the system pods of Kubernetes.
1204.59 -> And the other 2 are our simple-bank API deployment pods.
1208.13 -> OK, so that’s all I wanted to share with you in today’s lecture.
1212.379 -> We have learned how to deploy a web service application to the Kubernetes cluster on AWS.
1217.629 -> And we were able to send requests to the service from outside of the cluster
1221.629 -> Via the external IP or an auto-generated domain name of the service.
1226.13 -> But of course, we don’t want to use that kind of domain
1229.09 -> For integrating with the frontend for external services, right?
1232.779 -> What we would like to achieve is to be able to attach the service
1236.809 -> to a specific domain name that we have bought,
1239.44 -> such as simplebank.com or something like that, right?
1241.649 -> That will be the topic of the next video.
1244.999 -> I hope you enjoy this video.
1247.409 -> Thanks a lot for watching!
1249.38 -> Happy learning, and see you in the next lecture!

Source: https://www.youtube.com/watch?v=PH-Mcd0Rs1w