-In this lecture, we will discuss about imperative
and declarative approaches in Kubernetes.
Towards the end of this lecture,
we will talk about some tips that you can use in the exam.
So far, we have seen different ways of creating
and managing objects in Kubernetes.
We created objects directly by running commands,
as well as using object configuration files.
Now, in the infrastructure as CodeWorld,
there are different approaches in managing the infrastructure.
They are classified into imperative and declarative approaches.
Let's understand these with an analogy.
Let's say you want to visit a friend's house located at street B.
In the past, you would hire a taxi and give step-by-step instructions
to the driver on how to reach the destination,
like take right to street B, then take left to go to street C.
Then take another left and then right to go to street D
and stop at the house.
Specifying what to do and how to do more importantly
is the imperative approach.
On the other hand, today when you book a cab,
say through Uber, you just specify the final destination,
like drive to Tom's house.
This is the declarative approach.
In this case, we're not giving step-by-step instructions.
Instead, we're just specifying the final destination.
We're declaring the final destination and the system
figures out the right path to reach the destination.
Specifying what to do, not how to do,
is the declarative approach.
What's that got to do with what we are learning?
In the infrastructure as CodeWorld,
an example of an imperative approach of provisioning
infrastructure would be a set of instructions written step-by-step,
such as provisioning a VM, named a "web-server,"
installing the NGINX software on it.
Editing configuration file to use port 8080,
and setting the path to web files.
Downloading source code of repositories from Git,
and finally starting the NGINX server.
Here, we're saying what is required and also how to get things done.
In the declarative approach, we declare our requirements.
For instance, all we say is that we need a VM
by the name "web-server," with the NGINX software on it,
with a port set to 8080 and the path to the web files defined,
and where the source code of the application is stored.
Everything that's needed to be done to get this infrastructure
in place is done by the system or the software.
You don't have to provide step-by-step instructions.
Orchestration tools like Ansible, Puppet, or Chef,
or Terraform fall into this category.
In the imperative approach, what happens
if the first time only half of the steps were executed?
What happens if you provide the same set of instructions
again to complete the remaining steps?
To handle such situations, there will be
many additional steps involved, such as checks to see if something
already exists and taking an action based on the results of that check.
For instance, while provisioning a VM,
what would happen if a VM by the name
"web-server" already exists?
The same goes with creating a database or important data
should it fail or should it continue since the VM is already there.
What if we decide to upgrade the version of software
to say, NGINX 1.18 in the future?
It should be as simple as updating the version of NGINX
in the configuration file,
and the system should take care of the rest.
Ideally, the system should be intelligent enough to know what has
already been done and apply the necessary changes only.
That's the declarative way of doing things.
In the Kubernetes world, the imperative way of managing
infrastructure is using commands like the "kubectl run"
command to create a pod, the "kubectl create deployment"
command to create a deployment, the "kubectl expose" command
to create a service to expose a deployment,
and the "kubectl edit" command
may be used to edit an existing object.
For scaling a deployment or replica set,
use the "kubectl scale" command.
Updating the image on a deployment,
we use the "kubectl set image" command.
Now, we have also used object configuration files to manage objects
such as creating an object using the "kubectl create -f" command,
with the f option to specify the object configuration file,
and editing an object using the "kubectl replace" command,
and deleting an object using the "kubectl delete" command.
All of these are imperative approaches
to managing objects in Kubernetes.
We're saying exactly how to bring the infrastructure to our needs
by creating, updating, or deleting objects.
The declarative approach would be to create a set of files
that defines the expected state of the applications
and services on a Kubernetes cluster.
With a single "kubectl apply" command,
Kubernetes should be able to read the configuration files
and decide by itself what needs to be done
to bring the infrastructure to the expected state.
In the declarative approach, you will run the "kubectl apply"
command for creating, updating, or deleting an object.
The apply command will look at the existing configuration
and figure out what changes need to be made to the system.
Let's look at these in a bit more detail.
Within the imperative approach, there are two ways.
The first is using imperative commands such as the run,
create or expose commands to create new objects and the edit,
scale, and set commands to update existing objects.
These commands help in quickly creating or modifying objects,
as we don't have to deal with YAML files.
These are helpful during the certification exams.
However, they're limited in functionality and will require
forming long and complex commands for advanced use cases,
such as creating a multi-container pod or deployment.
Secondly, these commands are run once and forgotten.
They're only available in the session history of the user
who ran these commands, so it's hard for another person
to figure out how these objects were created.
It is hard to keep track of.
It's difficult to work with these commands
in large or complex environments.
That's where managing objects
with the object configuration files can help.
Creating object definition files, or configuration files
or manifest files as it's also called,
can help us write down exactly what we need the object
to look like in a YAML format,
and use the "kubectl create" command to create the object.
We now have the YAML file with us always
and it can be saved in a code repository like Git.
We can put together a change review and approval process
around these files so that a change made is reviewed
and approved before it is applied to a production environment.
In the future, if a change is to be made, for instance,
editing the image name to another version,
there are different ways to go about it.
One way is to use the "kubectl edit" command
and specify the object name.
When this command is run, it opens a YAML definition
file similar to the one you used to create the object,
but with some additional fields such as the status fields
that you see here, which are used to store the status of the pod.
This is not the file you used to create the object.
This is a similar pod definition file within the Kubernetes memory.
You can make changes to this file and save and quit,
and those changes will be applied to the live object.
However, note that there is a difference between the live object
and the definition file that you have locally.
The change you made using the "kubectl edit" command
is not really recorded anywhere.
After the change is applied, you're only left with your local
definition file which, in fact, has the old image name in it.
In the future, say, you or a teammate decide to make a change
to this object unaware that a change
was made using the "kubectl edit" command.
When the new change is applied,
the previous change to the image is lost.
You can use the "kubectl edit" command if you are making
a change and you're sure that you're not going to rely on
the object configuration file in the future.
A better approach to that is to first edit the local version
of the object configuration file with the required changes,
that is by updating the image name here,
and then running the "kubectl replace"
command to update the object.
This way, going forward, the changes made are recorded
and can be tracked as part of the change review process.
At times, you may want to completely delete
and recreate objects.
In such cases, you may run the same command,
but with the force option like this.
This is still the imperative approach because you're still instructing
Kubernetes how to create or update these objects.
First, you run the "kubectl create" command to create the object,
and then you run the replace command to replace the object,
or delete command to delete the object.
What if you run the create command if the object already exists?
Then it would fail with an error that says the pod already exists.
When you update an object, you should always make sure
that the object exists first before running the replace command.
If an object does not exist, the replace command
fails with an error message.
The imperative approach is very taxing for you as an administrator
as you must always be aware of the current configurations
and perform checks to make sure that
things are in place before making a change.
The declarative approach is where you use the same object
configuration files that we've been working on,
but instead of the create or replace commands,
we use the "kubectl apply" command to manage objects.
The "kubectl apply" command is intelligent enough
to create an object if it doesn't already exist.
If there are multiple object configuration files,
as you would usually, then you may specify
a directory as the path instead of a single file.
That way, all the objects are created at once.
When changes are to be made, we simply update the object
configuration file and run the "kubectl apply" command again.
This time, it knows that the object exists
and so it only updates the object with the new changes.
It never really throws an error that says the object
already exists or the updates cannot be applied.
It will always figure out the right approach to updating the object.
Going forward, any changes made on the application,
whether they are updating images
or fields of existing configuration files,
or adding new configuration files all together for new objects,
all we do is simply update our local directory with the changes
and then the "kubectl apply" command takes care of the rest.
We will discuss more about how the "kubectl apply" command
works exactly in the back end in the next lecture.
For now, let me give you some tips as part of the exam.
From an exam perspective, you could use the imperative
approach to save time as much as possible.
For example, if the question is to just create a pod
or a deployment with a given image,
then one of these imperative commands can help you
achieve that quickly, so it's important
to practice the imperative commands.
If you need to edit a property of an existing object,
then using the "kubectl edit" command
may be the quickest way.
If you have a complex requirement, say for example,
that requires multiple containers, environment variables,
commands, index containers et cetera,
then using an object configuration file
to create the object would be preferred.
This way if you see that you made a mistake,
you can easily update the file and apply it again
and using the "kubectl apply" command,
in that case, would be a better option.
For more details on the different approaches to managing
a Kubernetes cluster, get yourself familiarized
with the Kubernetes documentation pages
and in the upcoming lab exercises,
try to use imperative approach in solving questions.
I will see you in the next lecture.