Hey guys.
Welcome back.
In this section we're going to be developing and deploying a cloud native application to a Kubernetes
cluster.
We're going to work our way up towards doing that.
Before doing so I want to re-explore some very key concepts okay.
Number one is Kubernetes is simply a tool, a software that runs across a cluster of machines, that
runs across many machines, many nodes.
Some of these machines are master nodes.
The other machines are called worker nodes.
The master nodes are responsible for scheduling when and where your applications will run.
The worker nodes are what actually run your application.
They are the machines that provision the physical resources, the CPU and memory that your applications
need in order to run.
So if you take that cluster of machines, all of these machines, the worker nodes and the master nodes.
They all work together.
They all collaborate under the supervision of Kubernetes in order to provide you, the developer, with
a seamless experience when you're developing and deploying your cloud native apps to that Kubernetes
cluster of machines.
Okay, so what is a cloud native app?
Slide two depicts it as one that can be distributed into many microservices, each one running inside
of a container.
Okay.
Each microservice is like a mini application that performs a single function.
Each microservice.
Each containerized microservice is a building block of the overarching cloud native app.
If you take all of these building blocks, you end up with many microservice applications, all of them
working together to perform the complete set of functionality that is promised by this cloud native
app to all of its end users.
Okay, so a standard example is an e-commerce application that can be distributed across six different
microservice applications.
One microservice handles order management, another microservice handles the product catalog, one manages
the inventory, and so on and so forth.
All of them come together to provide a complete e-commerce experience.
Now, when you've got so many applications, you want to make sure that you don't have difficulty running
them reliably, regardless of the platform or environment that they've been deployed in.
That's where containers are very, very crucial.
Because containers only contain the application and the dependencies that that application needs in
order to run nothing else.
Containers are very portable because they isolate the application and its dependencies from the outside
world, ensuring that they can reliably run, no matter what platform or environment you deploy them
in.
All right.
Now in this section, we're only going to have one great submission app that can be distributed into
two containerized services, one great submission portal that acts as a front end so that users can
interact with it and submit great data.
And when it receives great data from the user, it's going to send it over to a great submission API.
That one is a back end, which contains the business logic for actually managing how the great data
is stored.
So one great submission portal front end that the user can interact with to submit great data, and
one great submission API back end that will manage the storage of the submitted great data.
So we've got all of these containers, and the best place to deploy containers is within a Kubernetes
cluster.
Because Kubernetes, remember, is a very powerful container orchestration tool.
But in Kubernetes, um, well, let's look at an analogy.
If you think of the atom in the physical world as the smallest unit of life in the world of Kubernetes,
the pod is the smallest unit of deployment.
Okay.
So although Kubernetes is this excellent container orchestration tool, you can't deploy a container
directly in Kubernetes.
It has to be wrapped inside of a pod.
Give me one second.
Guys, my battery is dying.
All right, let's plug this guy right in.
Um, yes.
You need to wrap your containers in a pod.
The reason is because, uh, the pod is a very specialized Kubernetes object that has the necessary
abstractions for Kubernetes to automate things like scaling, self-healing, resource allocation, so
on and so forth.
So to really reap the benefits that Kubernetes provides, we wrap the container inside of a pod.
Well, you have no choice but to wrap the container inside of a pod anyways.
When you're setting up a pod, we can distinguish between the metadata and the runtime requirements.
Okay, there is typically a 1 to 1 relationship between the pod and the containerized microservice that's
running in it.
So usually we just name the pod whatever the name of the microservice is.
Some people go as far as calling the pod the microservice.
That's valid too.
Anyways, first we specify the name, then we specify the label groupings.
Okay, labels are used to place the pod into logical groups.
So one group that this pod could belong to is the fact that it collaborates with another microservice
to provide a great submission experience.
So this container, this pod in this case belongs to the Group cloud native app.
Great submission.
Another group that this pod could belong to is the fact that it's a front end.
We could group it alongside every other microservice within this cloud native app.
That is a front end.
In this case, it's the only front end.
But use your imagination.
All right.
And the reason that we label pods into logical groups is because it then makes the pod really easy to
query and perform operations on it.
So I could say something like delete every pod.
That is part of the great Submission Cloud Native app.
And that operation would effectively query every pod that is in that group and delete it.
I could have an operation that says scale every pod that is of type front end.
It would effectively query all pods that have that front end label and scale it up.
All right.
So that's all for the metadata.
The next step is the more interesting step which is to define the runtime requirements.
So the containerized microservice that's actually going to run inside of the pod.
Okay.
Once the container is created we also specify the container port where the underlying microservice will
be serving requests.
Okay.
In this case the underlying app runs on port 5001.
That's just how it's been programmed.
And so what the pod is going to do is automatically expose this container port for internal traffic
within the Kubernetes cluster.
All right.
So after you've specified the microservice, the port that it serves requests on, then you specify
the resource requirements, the memory and CPU that you anticipate this pod will need in order to function
properly.
And what Kubernetes would do if we were running it on an AWS cluster, for instance, is it would look
for a node that can comfortably provision the requested memory and CPU in order to run the app.
But in this case, because we're just prototyping, we only have the one node our laptop.
But the resource requirements essentially decide which node our pod ends up running in.
All right.
That's all for theory.
Let's go ahead and implement all of this now.
All right.
We can start off by creating a new folder called I'm going to call it Kubernetes Is.
Course, this folder is going to contain all the project files that we develop in this course.
So take good care of it.
Launch it, launch your project workspace in an instance of VSCode.
And we're going to start by creating a folder called section one.
Section one is going to contain the pod definition for the grade submission portal pod.
All right.
And if you installed the Kubernetes extension then you should be able to just write pod.
And it will auto generate a skeleton for a pod definition.
All right.
And you'll remember that first we specify the metadata followed by the container runtime requirements.
There is typically a 1 to 1 relationship between the pod and the containerized microservice that's running
in it.
So typically we give it the same name as the microservice app.
So I'm going to say pod grade submission portal.
And the the other piece of metadata is labeling our pod so that we can identify it and categorize it
into distinct groups.
Now instead of just defining the application name that this microservice belongs to, I'm not just going
to say name, I'm going to say app.kubernetes.io.
It is recommended to preface your keys with this prefix, in order to avoid potential collisions with
other third party labels within the Kubernetes ecosystem.
So this prefix ensures adherence to Kubernetes labeling conventions.
All right.
So here we can start with the application name.
This great submission portal microservice belongs to the grade submission application.
It works alongside another microservice to provide a complete grade submission experience.
Okay, so the highest level of identification is the cloud native app name.
The second highest level is app dot Kubernetes.io slash component.
So this specifies the component or role of this microservice app within the overall application architecture,
one of them being a front end, the other being a back end.
Our great submission portal microservice is a front end.
Okay.
And then we get even more granular app.kubernetes.io/instance.
We end with the unique instance identifier, as it represents the most specific and granular level of
labeling.
It allows us to differentiate between multiple instances of the same great submission app.
Here we are setting up the Great Submission Portal instance.
So we've effectively used labels to identify our pod and categorize it into distinct groups.
This is going to help with querying and performing operations on the pod later on.
Now we specify the runtime requirements.
We're done with the metadata.
All right.
Within this pod we're going to create a container called Great Submission Portal.
Remember the 1 to 1 relationship between the containerized microservice and the pod itself.
All right.
And our container will be created from a container image.
If you've taken my Docker course or if you're familiar with containerization in general.
An image is a snapshot shot that captures a single piece of code and the dependencies that that code
needs in order to run nothing else.
So let me just show you something real quick.
So here is the great submission portal application that I coded up myself.
So here we have the Python file, the HTML, the CSS, the dependencies that this code needs in order
to run.
So I've taken this code as well as the dependencies, and I wrapped all of them up into a single Docker
image.
All right.
And I published that image to Docker Hub for public use okay.
So that you and I can pull this image during our lesson.
So you can go ahead and go to Docker Hub, search for my name and copy and paste the image name into
your Pod definition so that we can pull it.
All right.
If you're too lazy to go to my Docker Hub, you can just be very careful in copying this down and getting
it right.
All right.
So the container image is a snapshot a self-contained snapshot of our code and dependencies.
And we bring that image to life by creating a container out of it.
So the image contains the code.
And the container is what actually runs the code and creates a running process.
All right.
And when you've got a running process, you need to allocate the necessary amount of resources for that
process to run properly.
So we can distinguish between limits and requests.
I'll get to limits in a bit.
So requests what it is is it's the minimum amount of memory and CPU that the application needs in order
to function properly.
The minimum amount, the values that you specify here, they dictate which node our pod is going to
be scheduled on, because Kubernetes is going to look at the amount of memory and CPU that you're requesting,
and it's going to decide which node can most comfortably provision these resources for our app to run.
All right.
So this is the minimum amount of memory and CPU that our app is guaranteed to have when our container
gets deployed.
All right.
Now 500 Millicores for my very very small app is a bit excessive.
So I'm only going to give it 200 millicores of CPU.
The node that our app is running in is only going to give it 200 millicores of CPU, 128 bytes of memory.
Okay.
Now, before we move forward, talking about limits, I want to start off with some definitions.
Memory is the workspace where a computer stores and retrieves data for immediate use.
CPU is the compute that performs calculations and executes instructions.
You can throttle compute.
You can't throttle data.
So that makes CPU a compressible resource and memory an incompressible resource.
Okay.
What implications does this have if we don't place a limit on the amount of memory that this container
can use?
If you don't place a cap, then it's simply going to consume however much memory it can get its hands
on.
And if other containers start doing the same thing, this can lead to an out of memory situation for
the node.
And this out of memory situation on the node results in a disastrous situation where containers are
being terminated left and right.
Suddenly you have no more apps running.
Okay, so you typically never want your containers to exceed the amount of memory that they've been
initially allocated.
So we're going to set the memory equal to the.
So so we're going to set the memory limit equal to the exact amount that it will be initially allocated
by the node it's been deployed in.
Now for CPU you normally never want to place a limit on that.
Let me explain why CPU is a compressible resource.
If the node just happens to have some unused resources, we would want our app to take advantage of
it and use as much CPU as it can get its hands on.
And if our node ends up running out of CPU to a point where there's contention between containers to
claim some CPU, then the kernel scheduler will automatically throttle our container to just use the
amount of CPU that it initially was given.
All right.
Because CPU can be throttled, we don't need to put a limit on it.
Memory cannot be throttled, so we need to cap it to ensure that no excessive memory is used beyond
what's been initially allocated.
And we get this error, which I personally disagree with.
It says that no CPU limit was specified for this container and that we should the creators of Kubernetes
themselves, they actually advise not specifying CPU limits for your containers or rarely ever doing
so.
So what I'm going to do is I'm going to say command shift P, control shift P.
If you're using windows, I'm going to open settings JSON.
And then what I'm going to say inside of VS Kubernetes.
All right is disable Linters.
Resource.
Limits.
Okay.
Let me make sure I didn't misspell anything.
Then I'm going to say command shift P again.
Control shift P for windows.
Reload the window.
And the warning is gone now.
All right.
So in the grand scheme of things we've set up our pod.
We have our containerized microservice.
Our node is going to allocate this microservice a minimum amount of memory and CPU.
The last step is to specify the container port.
So I program the great Submission portal to serve requests on port 5001.
It follows that we need to expose a container port 001.
Where our containerized application can serve requests and our pod will automatically expose this container
port for internal traffic within our Kubernetes cluster.
Okay.
And that's pretty much it.
We're ready to deploy what we've got.
And another thing I want to say for memory and CPU there are no magic numbers.
It depends on the application itself, the nature of the things that it's doing.
Typically what you want to do is load test your application with your team, monitor its performance,
and determine what an appropriate amount of memory and CPU is.
In this case, I've already determined that for my app, so I'm going to be using these values.
Okay.
And now what I can do is say kubectl.
Well let me CD into section one.
All right I'm going to say kubectl apply dash f.
By the way if you've not used vs code before just go to terminal new terminal to open up a new terminal
session.
Anyways, I digress kubectl dash f so the file is great.
Submission portal pod.
From this pod definition we're going to create our pod.
All right so I can go ahead and say kubectl get pods.
Uh so here we have our pod that's been set up.
The container is currently being created I can say kubectl describe pod followed by the pod name to
get the status of the pod.
Uh, the container was created successfully.
The image was pulled.
Everything seems to be good.
Here we see the amount of CPU and memory that the that the container requested.
All right.
Everything is beautiful.
Now I can say kubectl get pods again.
And we see that our pod has one container in it that is running.
Everything is perfect.
Now, what's running inside of this pod is an actual application.
If I want to find out what's going on in this application, I can view that application as logs in one
of two ways.
I can say kubectl logs, view the logs for the app that's running inside of great submission portal,
and it outputs the logs from our app.
All right, now if I want to stream the logs in real time then I can say kubectl logs dash f stream
the logs within the containerized service running in the great submission portal pod And here I'm streaming
the logs.
Okay, so if we start getting requests or if the app starts outputting logs, it's all going to show
up in real time.
All right.
Now I'm going to open up a new terminal session.
And I'm going to say kubectl port forward and followed by the pod name.
I'll explain what port forward is in a second.
So if you imagine a boundary being our Kubernetes cluster, our great submission portal container can
only be accessed internally within the cluster.
I want to access this pod port where the application is serving requests on my local machine.
So what I can do is forward the port on this pod to my local machine port 8080.
This can be any value that you want.
And the pod port that I'm going to forward is 5001.
So first you specify the name of the pod whose port you want to forward.
Then you specify what that port is and which local port on your machine you want to forward traffic
to.
All right.
So now I'll be able to access my app on localhost 8080.
And any requests I make here will be redirected to pod port 5001.
And as I do that I want to stream the logs over here.
So I'll go ahead and say, uh, why am I using Safari or whatever?
Localhost 8080.
And I'm able to access the application.
Beautiful.
All right Now, um, what I'm going to do is stop this.
Stop this as well.
Now, what happens if I put an excessive amount of memory and CPU?
Like, what if I put something like 10,000 maybe bytes and five 50,000 millicores.
Okay.
I'm going to say kubectl delete pod.
Now you know what.
Let me use some label selection dash l I can query all pods that have the following label.
All pods that belong to the Great Submission app.
Oh my apologies.
Should be an equal sign.
My label selector was simply not properly formatted
All right.
So my great submission portal was effectively deleted.
I was able to use a label selector to query for my great submission portal pod and delete it accordingly.
That's where labels come into play.
They allow us to use label selectors to query our resources and perform operations on them.
That's why you typically always want to follow this, uh, broad to more granular format where you label
based on the cloud native app.
It belongs to what its role is within the application architecture.
And then you specify the actual instance.
Okay.
So we could have used any of these labels to query for our pod and delete it.
Anyways, I digress.
Now I'm going to say kubectl apply dash f.
Great submission portal pod dot YAML.
With these extravagant memory requests Oh request can't be less than limit.
That should make sense, because the memory requests is the minimum amount that it can have.
And this is the cap.
So we'll just set it to be the same thing.
All right.
We'll have to delete our pod again I believe.
Oh no need.
Okay.
We'll reapply the definition.
Great submission portal created.
Okay.
Now I'm going to say kubectl.
Let me clear the output.
Kubectl get pods.
Notice that it's pending.
It's trying to find a node that has the resources to provision this amount of memory and CPU for our
container.
The node in this case is my personal machine, and it definitely doesn't have this much memory and CPU
to give a single container.
Okay.
So if I say kubectl describe this pod.
You can see that it says zero out of one nodes available.
Insufficient CPU insufficient memory.
My node which essentially forms this Kubernetes cluster.
It doesn't have enough resources to give this container.
So the overall pod just always remains in the pending state.
All right.
So we can go ahead and delete this pod by simply saying kubectl delete pod.
Instead of using a label selector to query the pod based on the group that it belongs in, I can simply
delete the pod as well based on its name.
All right, now label label selectors become more handy once we talk about service discovery.
That was just a demo to show you that we query based on labels in Kubernetes.
But anyways, we've deleted that.
Let's reset our container to use meaningful values, and that's pretty much it for deploying a microservice
app in Kubernetes.
All right.
Let's go ahead and wrap this lesson up with one final topic a small topic I promise.
So a pod can actually run more than one container.
Okay.
Um, does that mean we would want to run the great submission API inside of the Great Submission Portal
pod?
That wouldn't make any sense.
Remember the 1 to 1 relationship between the pod and the containerized microservice that's running in
it?
It doesn't make sense to have two separate microservices with distinct roles and distinct responsibilities
running inside of the same pod.
Running two containers inside of the same pod implies that the containers need to be created and terminated
at the same time.
They need to be scaled up or down at the same time, these are two very separate applications and need
to be scaled and created independently.
All right.
So when would you ever want to have two containers in the same pod?
When the microservice relies on a sidecar to provide it with additional behavior and functionality?
In this case, the great submission portal microservice, which is still the meat and potatoes of this
pod.
It's the core component of this pod.
It relies on a great submission portal health checker in order to monitor its health and display information
about it.
Okay.
The great submission portal health checker.
It cannot exist without the great Submission portal microservice.
It needs to be run inside of the same pod as the Great Submission Portal microservice, so that when
we create the pod, they are both scheduled on the same node at the same time, when we destroy the
pod, both containers are destroyed.
Together, this container cannot exist without the Great Submission Portal.
Okay, now I said something really important.
Uh, both of these containers, by virtue of running in the same pod, they're going to be scheduled
to the same node.
They're going to be scheduled to the same host.
They are going to share the same networking, the same network namespace, which means that these two
containers can communicate with each other via localhost.
This is really convenient because I programmed the Great Submission Portal health checker to make requests
on localhost 5001 to make requests to the great submission portals.
This container port 5001 at the Great Submission portals health endpoints.
Okay, so this health checker is constantly making requests to the great submission portals health endpoint
on its container port and then it displays information about it.
That's all it is.
Now, this great submission portal health checker, I packaged it and all of its dependencies into a
Docker image, um, which is available for public use.
So go to my Docker Hub registry are slim 087.
You can look for the following image, or you can just copy it down to your pod file.
Oops.
Don't want to do that.
So let me just copy it down here.
For now.
We're going to have two containers running inside of this pod.
So let me go and copy this.
The second container is the great Submission Portal health checker.
This container is going to be created from the image our slim 087 slash Kubernetes course grade submission
portal, health checker, uh, resources.
We'll leave them the same.
Now, this is not a web application, so we don't need to specify a container port.
It's not going to be serving any traffic.
All it's going to be doing is making requests to the great submission portal on its container port.
5001 all right.
That's really all we need.
I'm going to go ahead and say kubectl apply dash f.
We're going to reapply this pod definition and create a pod with two containers.
Beautiful kubectl get pods.
Now I've got one pod with two containers.
Both of them are ready yet both containers are being created based on the images that we're pulling
from my Docker Hub registry.
Okay.
Uh, let's say kubectl describe we'll describe the status of this pod.
Oh.
What am I doing.
Kubectl describe pod.
Great submission portal.
So first as it says you specify the resource type followed by the name of that resource started container.
Great submission portal.
Health checker started container.
Great submission portal.
Beautiful.
So if I say kubectl get pods again everything is working as it should.
We have one pod running to containers inside of it.
So what I'll do is on a separate terminal I'm going to clear.
We don't need to port forward this port to a local machine port, because the great submission portal
health checker is in the same pod as the Great submission Portal, and is going to be making internal
requests to it via its local host.
All right.
So here what I'm going to do is say kubectl logs dash f here I want to view the logs or I want to stream
the logs.
Remember dash f allows us to stream the logs of a container in real time.
Now remember before all we had to do was specify the pod and it would automatically stream the logs
for the container that's running in it.
But this time we've got two containers.
So what we need to also do is specify the container for which we want to view the pods.
In this case I want to view the pods for I want to view the logs for the container.
Great submission portal this one right here.
And you can see that our great submission portal health checker is already making all of these requests
to its health end point.
I programmed the great submission portal microservice.
To output logs whenever requests are made to its endpoint.
And these requests are working out really well.
Status 200.
So now what I'll do is I'll say kubectl logs dash f over here.
Great submission portal.
We want to view the logs for one of the containers inside of the Great Submission Portal pod.
We want to view the logs for the great submission portal health checker.
Okay.
And you can see it keeps making requests.
The sidecar keeps making requests to the Great Submission portal via localhost 5000 001.
Those requests are always working, which confirms that our application, our great submission portal,
is healthy.
So you ever you only ever really want to run two containers in the same pod when the main container,
the meat and potatoes of the pod, relies on additional behavior and functionality by a sidecar app.
So this is the microservice because it forms a core component.
It is a building block of the cloud native Great Submission app.
And this is a sidecar application because all it does is it provides additional functionality for our
great submission portal app.
All right.
So that's it.
We've deployed a microservice.
We've deployed a microservice with a sidecar alongside it.
That's all we're going to cover for this video.
And the next one we're just gonna it's just going to be pretty much extra practice.
And we're going to go through things a lot faster.
Uh, we're going to deploy the great submission API pod so that we can eventually have these two collaborate
and give us the full, great submission experience.
I'll see you in the next one.