Let's get started with
the solution video for Mock Exam 3.
Before we begin,
I just want to quickly highlight,
if you go to the Linux Foundation
documentation page on the important
instructions for CKA and CKAD,
you get a lot of details here about the exam,
which we have discussed.
One thing I want to highlight
is if you scroll down,
you will see this section where
it says all environments,
the base system and cluster nodes,
have the following additional
command-line utilities installed.
You have the kubectl,
the k alias and Bash autocompletion.
All of these are already done.
You have also the jq utility
and then tmux and curl
and other are also there.
You don't really have to worry about
configuring this or installing this
in the exam environments.
Let's get started with the first question.
Create a new serviceAccount
with the name pvviewer
and grant the serviceAccount access
to list all PersistentVolumes
in the cluster by creating
an appropriate clusterrole
called pvviewer-role
and ClusterRoleBinding called
pvviewer-role-binding.
Then create a pod called pvviewer
with the image redis
and serviceAccount pvviewer
in the default namespace.
A few things to do with respect
to serviceAccount, clusterroles,
and ClusterRoleBindings,
and then finally to create a pod using those.
Let's get started.
The first step is to create a serviceAccount.
Let's do a kubectl or the short form k ctl,
create serviceAccount.
Let's us check the help real quick.
It's just creating serviceAccount
and serviceAccount name.
It's that simple. Create serviceAccount,
and the account name is pvviewer.
The next step is to grant
this serviceAccount access
to list all PersistentVolumes in the cluster
by creating an appropriate clusterrole
called pvviewer-role.
Let's create a role.
That's kubectl create clusterrole.
Let's check the help.
You have kubectl create clusterrole,
the role name,
and then the verb and resource.
Let's pick that.
The clusterrole is going to be
pvviewer-role,
and the verb is just list,
and then you have--
It is to list all
PersistentVolumes in the cluster,
so this has to be PersistentVolumes,
but if you're not sure if it's PersistentVolumes
or just PersistentVolume,
what you can do
is I'm just going to park it there.
Let's just do a kubectl api-resources
and look for Persistent.
All right.
We have PersistentVolumeClaims
and PersistentVolume.
This is what we want,
so let us go back to that
and just paste that in.
It's PersistentVolumes.
That should be that.
The next step is to create
a ClusterRoleBinding called
pvviewer-role-binding to bind
that serviceAccount to that clusterrole.
That's kubectl create ClusterRoleBinding.
Let's take a look at the help.
Create ClusterRoleBinding, the name,
the clusterrole.
Here you have user and group,
but that's not what we want.
We want serviceAccount with namespace
and serviceAccount.
That's what we're going to do.
It's like that.
The ClusterRoleBinding name has to be this,
the clusterrole is this,
and we want serviceAccount,
just serviceAccount=.
Then the serviceAccount that we created
is pvviewer.
We're going to do that.
When we do that,
it says it must be
in the namespace name format.
Just remember to put in
the default namespace so it always
requires a namespace,
so even if you accidentally forgot it,
it would tell you, give you this error message,
and you'll know that
have to put in the namespace.
That part is done.
Let's quickly verify.
Describe clusterrole
pvvviewer-role.
We have PersistentVolumes in list.
That's good.
Then we have ClusterRoleBinding,
and it's role-binding.
That's for serviceAccount pvviewer
in the default namespace.
The role is pvviewer-role.
That's good.
Next, we have to create a pod.
Let's do a kubectl run pvviewer
with the image redis,
so that's image=redis
and the serviceAccount pvviewer.
We do have an option with the run itself.
We run command itself
to provide a serviceAccount.
That's just serviceAccount=
and then we can just specify
the serviceAccount.
If we do a kubectl get pods,
you see that we have the pvviewer pod.
It's in a container creating state,
and it's now in a running state.
If you look at the pod
in the YAML output,
we can see if it has serviceAccount.
Yes, here we have the serviceAccount
and serviceAccount name,
which is correct.
That's about that question.
Let's go the next one.
In this question,
we have to list the InternalIP
of all nodes of the cluster,
and we have to save the results
to a file node_ips.
The answer should be in the format
InternalIP of controlplane
InternalIP of node, et cetera.
Let''s do a kubectl get nodes.
These are the nodes.
Let's do a wide.
Here, we get the InternalIPs.
The goal is to get this in the form--
If there are multiple nodes,
then in order to get it in a particular format,
which is basically
all the InternalIP addresses of all the nodes,
and then direct that to this file.
For this, we use the JSONPath.
Let's go to the kubectl cheat sheet
and look for JSONPath options.
Here, we have get nodes
and the JSONPath,
different options for JSONPath.
Let's let's try and find out
how we're going to identify where that is.
We need to find out InternalIP.
Let's just do a -o json.
This gives us this in JSON format.
Now, what we need to do is,
if you use the jq utility,
it formats well so you can see it
in a better way.
Now, there's so much information,
and it's going to be hard to find out
where the information
that you're looking for is.
One thing we could do is we could use
the -c option for JSONPath.
It has something called as paths.
That's basically jq -c and then 'paths'.
It's going to list all the paths to every field
within this, within the output.
If we now search for InternalIP here,
we should be able to see some information.
If you look at it,
most of these are within
the metadata and managedfields.
All of these have this f before it.
These are basically just fields
that are part of the metadata.
This is not what we want.
We want to find out
the actual current IP address.
If we go back and look at this output,
one thing we could do is go back
and look at this output
I grep for InternalIP.
We see that InternalIP is actually
a value for what's called as type.
Let's say, if we do a 5 before
and 5 after just to see a little bit extra,
we see that there's something called
a status and then you have address.
Within addresses, you have these addresses.
The InternalIP is just a type.
What we're really looking for is type,
so let's go back.
Let's go back to the JSONPaths that we had.
Instead of searching for InternalIP,
let's search for type.
We see a lot of stuff.
Now, as we discussed,
we don't want the metadata sections,
so let's just try and remove those,
so "metadata".
We get a much smaller list this time.
If you look at it, you have items,
status, addresses, and type.
There's also item, status,
conditions, and type,
but we're not really interested in conditions.
It's ideally items, status, addresses,
and type.
Maybe we can just further filter that
to just search for address.
We get these.
This is comparatively a complex example,
but these are different ways that you can--
These are different techniques
that you can follow to identify the path
that you can use in the JSONPath query
that you're going to use.
Here, just by filtering, just by using paths,
it lists all the paths.
Then from that,
we just filter down the stuff
that we actually needed.
You have items, and then this is an array.
If you have like 10 nodes,
there's going to be 10 of these.
Then you have status,
and then you have address,
and then this is an array,
and then you have type.
That’s what we are going to follow.
Let's do a kubectl get nodes -o JSONPath.
We're going to have this format.
Then within here, we're going to use items,
just items.
JSONPath, it has to be equals,
JSONPath=items,
that’s .items.
If you want to see this in a format
where you could use,
you could just pass in jq at the end of it.
You have items,
and we know that items is an array.
If we look at the output that
we originally got from this,
so you have items,
and then you have an array,
and then you have status, addresses,
and then array and type.
You have items and then you have an array.
If you want the addresses
for all the items in the array,
that's all the nodes, then you can put a *.
If you're just looking for the first one,
you can just go to 0.
Let's just do one at a time.
Let's just do a 0.
This basically gets me the item 0,
which is node 0,
and then you have status under it.
Let's put status.
This is just a status section.
Then under status, you have--
You have status and then you have addresses.
Let's put addresses and you get this.
We have items of 0, so this is the first node,
and then you have status,
and then you have addresses.
These addresses is an array.
That’s what we saw here.
Let's just run that command again.
Status and then addresses.
Addresses is an array,
which is what you can see
by these brackets here.
Then it has two items.
What we could do is just--
If you want to print all of the--
everything within
these particular addresses--
All right.
There are two items--
you could just put a * and then say .
What we really need is this address,
so that's what we're going to put.
There's no longer--
You use jq because we're no longer
getting JSON format at output.
You have addresses
and then you have .address,
and that gave us the IP address.
It says controlplane,
and this controlplane is basically this.
You have the IP address and controlplane.
Controlplane is just the hostname,
and this is actually the InternalIP.
We don’t need this.
We don’t need the hostname
because it does not ask us
to print the hostname.
It only asks us to print the InternalIP.
This is where we could use a filter
to filter this out of this
and just only have this.
The goal is from out of this array,
we filter out this that we don’t want,
and we just get this.
For that, there's this example of a filter,
if you can see here.
For example, this one.
Within addresses, there's this filter
that just filters out ExternalIP,
but we're going to use the same filter,
and we're going to use
InternalIP in this case.
Instead of *, we're put that filter,
and then put that query,
and then we're going to put InternalIP.
Now, we have the InternalIP
of one node printed.
Just to get InternalIP of all the nodes,
all we have to do is, instead of 0,
we just put a * here,
and we get the InternalIP of both the nodes.
Then the goal is to just redirect that to file,
and then make sure that file
has that information.
That’s what this is about.
The first step is to identify the path
to what we want to retrieve,
and then instead of the 0s and 1s,
you always put a *
depending on what you want to display.
Then in this case,
we wanted to just get the 0s only,
so we just put a filter there.
The same thing is going to work
if you just put 0,
because 0 InternalIP is always for 0,
but just to be more accurate in case
there are multiple items,
in case there are like ExternalIPs,
InternalIPs and hostnames
to always actually just fetch InternalIPs,
which is more reliable.
Something like this is more reliable.
There are many other examples here
so you can follow.
If you can figure this out,
then you can most likely figure out
any kind of JSON queries
that you might come across.
That's that.
Let's go to the next question.
Here, we have to create a pod called
multi-pod with two containers.
Container 1 with the name alpha
and image nginx.
Container 2 with the name beta
and image busybox.
The command, sleep 4800,
this is for the busybox container.
Then you have environment variables.
Container 1 should have name in alpha,
and Container 2 to have the environment
variable name beta.
That's what it is.
We know that we can create
a container report using
the kubectl run command.
The goal is to create a pod
with multiple containers.
It's going to be kubectl run multi--
It's basically called multi-pod
for multi-pod container,
multi-container pod and two containers.
I could use the imperative command
to create a pod with a single container,
but I cannot use the imperative command
to create a pod with two containers.
I'm going to have to pick
one of these containers and create with it,
and then add in, and do a dry-run,
redirect it to a file,
and then add the other details.
What I'm going to do
is I'm going to pick this one
because it's just easier to just put
more additional details.
I can specify in the command line
as part of the imperative command.
That way, once I put it to a file,
I just have to easily add the other pod.
I'm going to pick
the bit more complex container,
which is the one that has sleep,
that we later add.
I have less work,
so I'm just going to do
a kubectl run multi-pod.
Image is going to be busybox.
We have a command,
so I'm going to specify command.
The command is going to be sleep 4800.
Now, I'm going to do a dry-run.
dry-run=client.
I'm going to output that to a YAML file.
Let's check that out.
I just lost my alias.
I'll fix that.
Here's the file.
I'm going to redirect that to multi-pod.yaml,
and then I'm going to edit the file.
Here, we have pod called multi-pod,
so the name of the pod is multi-pod.
That's fine.
The first container--
This is actually the first container.
It says the first container should be alpha,
but in this case it's the busybox.
Anyway, we're just going to add
the first container in.
The first container name is going to be alpha,
and the image that it's going to use is nginx.
Then the second container
already has the command set to sleep 4800,
the image is already set to busybox.
The name is not multi-pod,
so we're going to change that to beta.
Then you have the DNSpolicy, restartpolicy,
but then we also want to set
environment variables.
Container 1 should have
the environment variable name alpha.
Container 2 to have
the environment variable beta.
Here, we're going to add
environment variables,
but if you're not sure,
you can quickly search just to be sure.
You have ENV, and then you just need
a name and value.
You're going to have have ENV,
and you're going to have name.
It's name, alpha.
Environment variables are to have
name and value,
so name is going to be name,
and value is going to be alpha.
Similarly, we're going to have one for this.
ENV, we're going to have name
goes with name
and value is going to be beta.
Yes.
That's that.
Let's go ahead and create the file.
Let's now check the status of the pod,
to see that it's in a creating state.
Let's do a describe pod multi-pod.
We see that it has two containers,
alpha with the image nginx,
and environment variable name alpha,
and then beta with the image busybox,
and the command sleep 4800,
and the environment variable name beta.
That's good.
Let's proceed to the next one.
The next one is to create a pod
called a non-root-pod
and with the redis:alpine image
with the user 1000 and group 2000.
We're going to do a kubectl run non-root-pod,
and the image is going to be redis:alpine.
We're just going to do
a dry-runs=client -o yaml.
We get the file,
and we're just going to redirect that
to non-root-pod.yaml.
Then we need to set the security context,
so let's just search for security context.
Here, you have an example
of security context for runAsUser
and runAsGroup and fsGroup.
What we really need is group.
We're going to have that.
The runAsUser is 1000, which is good.
fsGroup is the next one,
and that is 2000.
That's good enough.
Let's read file
and let's do a describe.
Here, we have the containers.
You have non-root-pod.
The image is redis:alpine.
You have to look at--
Let's look at the output in YAML format
to verify the security context set.
You have the security context,
and you have the fsGroup 2000 runAsUser 1000.
That's good.
Let's go to the next one.
We have deployed
a new pod called np-test-1
and a service called np-test-service.
We have np-test-1,
and we have np-test-service.
Incoming connections to this service
are not working,
so troubleshoot and fix the issue.
Create the network policy
by the name ingress-to-nptest
that allows incoming connections
to the service over port 80,
and don't delete
any current objects deployed.
First, to test this, we want to--
We know that there's an issue,
but we want to test it first,
and find out what the issue is,
and then apply our fix,
and then we want to test it again
to make sure that it's working.
Let's run a temporary container.
Let's run, let's say, a call or something.
You could use busybox
and do an n-KAD[?],
or if you just want to do a curl
because the port is 80,
you can just do just use alpine curl.
I'm just going to call it curl,
and then we call it alphine.
Oh.
What we want to do
is we don't want to just run it.
We're only getting inside
and just run a curl.
I'm going to put the --rm to remove it
once the container is killed,
and then I'm going to put a -it
and just sh into that.
I forgot to specify the image.
What I want to do is I want to run this,
run a temporary pod called curl
using the alpine/curl image and then,
I’ll just get into it using the exec sh to shell,
and now we can do a curl.
I'm going to do a curl np-test-service.
If it's working, it should return me
whatever it is running on pod 80.
That's what I'm going to try here.
It doesn't look like it is responding,
so let's leave that there
and let's open a new terminal.
We're going to start working on
fixing the issue.
The goal is to create a network policy,
so let’s search for network policy.
Here, we have sample of network policy.
What we want to do is we want
to create a network policy
for the np-test-1 pod so that it can allow
incoming connections on pod 80,
so that's the goal.
We need to create a network policy
for ingress and allow anything
that's coming on pod 80,
so that's the goal.
Let's copy this.
Let's create network policy .yaml.
We need to give it a name that's given here,
so that's the name.
The pod selector is supposed
to be whatever labels are set for this,
so I'm not sure what that is.
We're going to find that out.
Kubectl get odds
and look at it in YAML format.
We see any labels set on it.
You have run np-test-1,
so that's the label.
That's what we were going to use
to run np-test-1.
All we need is ingress,
so we're going to remove egress.
For this use case, it's actually pretty simple.
It should allow incoming connections
to service whole pod 80,
so we don't have a limit to any specific
name, selector, or a pod, or IP addres.
We can get rid of all of this, get rid of this.
All we need to do is ports, so TCP port 80.
We won't allow ingress traffic
to any on pod 80 from anywhere.
Let's save that.
Let's create this file.
Let's go back and test this again.
This time, we see that
we immediately got a response,
so this is basically an nginx server.
We got the welcome to nginx.
That confirms that the issue is resolved.
As soon as we exited the curl,
the pod was deleted.
That should help with that.
The next one is to taint the worker node,
node01, to be unschedulable.
Once done, create a pod called dev-redis
with the image redis:alpine
to ensure workloads are not scheduled
to this worker node.
Then finally, create a new pod
called prod-redis
and the image redis:alpine
with toleration to be scheduled on node01.
The key to use is the environment type.
The value is production,
so that's how you should taint it.
Operator is Equals,
and the effect is NoSchedule.
These details are given,
so the goal is to taint the node
with this information and then create a pod,
dev-redis, to make sure that
the taint is working so that
it's not sheduled on node01,
and then we have to create another pod,
prod-redis,
with the toleration so it's set on that,
on node01.
Let's first do a kubectl taint.
Let's check the help.
We have node, node name,
and the environment key, and the effect.
We'll just use that.
Node is node01.
The key is env_type.
The value is production,
and the effect is NoSchedule.
Let's do that.
Let's check the node now.
Kubectl describe node01.
We see the taint set as expected,
so we have environment type,
and production, and NoSchedule.
That's good.
The next step is to create
a pod called dev-redis.
Kubectl run dev-redis, image=redis:alpine.
That's it.
Let's run that.
Let's look at the pod
and see where it is scheduled.
As you can see, the dev-redis pod
is scheduled on controlplane,
because it doesn't have the toleration
for node01.
It's running, so that's good.
The next step is to create
a new pod called prod-redis.
Let's do a kubectl run prod-redis.
The image has to be redis:alpine
with the toleration to be scheduled on node01.
Let's move these two.
Let's do a dry-run=client,
and let's output this to YAML format.
Let's call it prod-redis.yaml
and then we do--
Let's edit this,
and we have to specify the tolerations.
Let's go here and do tolerations,
taints and tolerations,
or something like this, just any toleration.
Specify the toleration.
Let's add it at the end.
I'll just fix the alignment.
The key is, again, environment type,
operator is equal, the value is production,
and the effect is NoSchedule.
That should be that.
Save that and let's create that pod.
Let's do a describe pod prod-redis
and let's see it has
tolerations environment type,
production, and NoSchedule,
and let's see which node it is assigned to.
It's assigned to node01.
You can get a better view
if you run the get pods -o wide.
We see that we have the prod-redis
and the schedule on node01.
That seems to be good.
The next one is to create a pod
called hr-pod in the hr namespace
belonging to the production,
environment, and frontend tier.
There's label, the hr-pod has to be labeled
environment=production
and tier=frontend,
and the image to use is redis:alpine.
That's pretty straightforward.
First, let's look at the pods
in the hr namespace, the -hr.
It says no resource was found
in the hr namespace.
Let's do a get namespaces.
We see that there are no namespaces by hr,
so that's something we have to do first.
Let's create a namespace
called hr first, so that's the first step.
Next is to create a pod,
so we're going to do a kubectl run hr-pod,
and in the hr namespace,
so that's for the hr namespace,
and belonging to the production,
environment, and frontend tier,
and those are labels.
We're going to add
labels=environment=production,
tier=frontend.
If you're not sure how labels work,
you' could do a kubectl run
and help and just make sure the labels
are specified in the right format.
You have kubectl run, pod name,
the image name, and then you have labels.
The labels are specified,
comma-separated within quotes.
The quotes are not mandatory,
but let's just put the quotes in.
Oops, I missed the image again.
Yes, image has to be redis:alpine.
Specify that.
Let's check the pod hr-pod
in the hr namespace.
We see the label set
environment=production
and tier=frontend.
That should be good.
The next one, question eight.
A kubeconfig file called super.kubeconfig
has been created under
the root/CKA directory.
You have root/CKA,
and there is indeed a super.kubeconfig file.
There's something wrong
with the configuration,
so troubleshoot and fix it.
We know that we could do a kubectl,
let's say, get nodes or any kind of command,
and specify the kubeconfig file
using the --kubeconfig option.
We'll pass in a CKA.
By default, the kubectl utility
uses the kubeconfig file,
which is under the .kube directory
in the home directory of that user.
In this case, we have to use
another kubeconfig file,
which is the super.kubeconfig file.
For that, you could specify
the --kubeconfig option.
We have root/CKA
and super.kubeconfig.
When it's trying to connect
to the server, it says,
"The connection to the server
controlplane 9999 was refused.
Did you specify the right host or port?"
If you look at it, of course,
it looks like either the hostname
or port is wrong.
It looks like this port is wrong,
because this is not the port
that the kube API server runs on.
If you're not sure,
we could actually look at this file.
If you look at the file,
you'll see it has the API version,
the cluster details.
Here, you have the server, and it says,
"Controlplane 9999."
That seems to be wrong.
We're not sure what the port--
You could look at the current kubeconfig file,
.kube/config.
You'll see the port is 6443.
That's the port that it should run on.
Let's go ahead and fix it.
/root/CKA/super.kubeconfig.
Here, we have--
Instead of 999,
we're going to change it to 6443.
Let's test.
Let's test our work again using this command,
get nodes with the custom kubeconfig file.
That seems to be working.
Let's go to the final question.
We have created a new deployment
called nginx-deploy.
Scale the deployment to three replicas.
Let's take a look at that.
Let's do a kubectl get deploy.
We see that there is a deployment
called nginx-deploy.
Then what we need to do
is we need to scale it up,
so let's do a kubectl scale
deploy nginx-deploy
and specify replicas to be three.
Let's run a kubectl get deploy.
We see it's one out of three ready,
so let's take a look at the deployment
in more detail.
We expect it to be three out of three ready.
As you can see here,
it scaled up the replica set to one.
We currently expect it to scale it up
to three but that's not happening.
That means that has not been initiated.
We know that the deployment,
or any kind of controllers,
are managed by
the kube-controller-manager.
Let's take a look at--
Only if the kube-controller-manager
is not functioning properly,
this can happen.
Let's take a look at the pods system.
We see that there is the
kube-controller-manager
indeed has some issue,
so we're going to take a look at--
Let's take a closer look at that pod.
We see that it's failed to create a pod.
It's an error response.
There's also ImagePullBackOff error,
so this image isn't right.
There seems to be something wrong
with the image.
If you take a closer look,
you'll see that there's a 1
instead of an L here,
as it's supposed to be controller-manager
but it is just contro1ler-manager,
so that's wrong.
The kube-controller-manager
is run as a static pod
because you can see the controlplane
appended to the end of it.
The manifest file for that is under
etc/kubernetes/manifests
and you have kube-controller-manager.
We need to edit this file and fix that.
If you look at this image--
Let's take a look at the image--
you see that you have the kube-contro1ler.
You could search for that.
Let's just search for contro1.
If you see, there's also a typo here.
There's a contro1 here instead of L.
If you just keep searching,
so you have contro1.
If you keep searching,
you can see that
it's the same in multiple places,
so that's the third occurrence.
Then there's the fourth occurrence,
and the name as well has contro1 in it.
There's one, two, three, four, five.
It's incorrect in five different places,
so let's go ahead and fix that.
That's L, and that's another one.
That's another place,
and then you also have it here.
If you could just do a "replace all",
then that works too.
If you're familiar with just doing
a "replace all" with SED,
then that works too.
Let's search again.
There are no more instances,
so that's good.
Let's save it.
We now see that it's in a running state.
Let's check our deployment.
It's still one of three,
so let's give it some time.
That's it.
It's now three out of three,
so that indicates the issue is fixed.
All right.
We're at the end of the mock exam.
Let's test our work by ending the exam.
That's 44%, 58%, 70%, 78%,
enough to pass the exam,
and it's 100%.
Well, I hope that was helpful.
Thank you for watching.
I wish you all the best for your exam.