At a glance
Nana Janashia teaches the whole of Kubernetes in a single sitting, and she does it by building one small application and letting each component enter the story only when the application actually needs it. The first half is concepts: what container orchestration is, why the rise of microservices made it unavoidable, how a cluster is split into a master node running the control plane and worker nodes running your containers, and then pods, services, ingress, ConfigMaps, Secrets, volumes, Deployments, and StatefulSets, each introduced as the fix for a specific problem the previous slide just created. The second half is hands on: install Minikube, start a one node cluster with the Docker driver, write four YAML files, and deploy a MongoDB database plus a Node.js web app that reads its database endpoint from a ConfigMap and its credentials from a Secret, reachable in a browser through a NodePort service. This page rebuilds the entire course in her order, with every command in a shell block, every manifest printed in full and valid, and every field explained rather than pasted. Follow it top to bottom and you finish with the same running application she finishes with.
The plan she lays out (0:00)
She opens with the promise: everything you need to know to get started with Kubernetes, in one hour. She has taught hundreds of thousands of people to advance their DevOps skills through her YouTube channel, her online courses, and the DevOps educational program.
The overview of the hour, in four movements:
- What Kubernetes is, why we need it, and why it became so popular.
- The Kubernetes architecture, so you see how Kubernetes actually works in the background.
- The main Kubernetes components you need in order to work efficiently with it day to day.
- A hands on demo project, so you finish with real practical experience rather than slides.
She is honest about the shape of the thing before starting: Kubernetes is a very popular but also a very complex technology, so a crash course gets you your first experience and no more. If you want to go from there to building, configuring, and managing clusters from scratch, and to passing the CKA exam from the Linux Foundation, that is what her Kubernetes Administrator course exists for. Then: we have a lot to cover, so let us jump right into it.
What Kubernetes is, and why microservices forced it (1:44)
The definition first, and it is worth reading slowly because every word in it is load bearing.
Kubernetes is an open source container orchestration framework which was originally developed by Google.
At the foundation, it manages containers. Docker containers, or containers from some other technology, it does not care. Which means, in practice, that Kubernetes helps you manage applications that are made up of hundreds or maybe thousands of containers, and it helps you manage them across different environments: physical machines, virtual machines, cloud environments, or even hybrid deployments spanning several of those at once.
So what problem does it actually solve? She walks the history chronologically, because the answer is historical rather than technical.
The rise of microservices caused increased usage of container technologies, because containers offer the perfect host for small, independent applications. That is the fit: one small service, one image, one runtime environment, no shared machine state. But the rise of containers plus the rise of microservices produced a second order effect nobody designed for. Applications are now comprised of hundreds, sometimes thousands, of containers. Managing that many containers across multiple environments using shell scripts and self made tools can be really complex, and sometimes it is simply impossible.
That specific scenario, and not any single vendor's ambition, is what created the need for container orchestration technologies.
The three things an orchestrator guarantees (3:02)
What tools like Kubernetes do is guarantee three features, and she names them explicitly because they are the reason the complexity is worth paying for.
- High availability. In simple words, the application has no downtime. It is always accessible by users.
- Scalability. You can scale your application up fast when load increases and more users are hitting it, and scale it back down just as easily when the load drops. The application becomes flexible about load rather than provisioned for a guess.
- Disaster recovery. If the infrastructure has a problem, data is lost, servers explode, something bad happens in the data center, then the infrastructure has to have some mechanism to back the data up and restore it to the latest state, so the application loses no data and the containerized application can pick up from the latest state after recovery.
All three are functionalities that container orchestration technologies like Kubernetes offer out of the box. Hold on to the third one in particular, because when she gets to etcd you will see exactly where the backup comes from.
The cluster architecture (4:33)
A Kubernetes cluster is made up of at least one master node, and connected to it, a set of worker nodes.
Every node in the cluster, master and worker alike, runs a kubelet process. The kubelet is the Kubernetes process that makes it possible for the cluster to talk to itself, node to node, and to actually execute tasks on those nodes, such as running application processes.
Each worker node has containers of different applications deployed on it. Depending on how the workload is distributed you will have a different number of containers running on each worker node. Worker nodes are where the actual work happens: this is where your applications run.
Which raises the obvious question. If the workers run the applications, what is running on the master node?
The master node processes (5:37)
The master node runs several Kubernetes processes that are absolutely necessary to run and manage the cluster properly. Four of them matter to you immediately.
API server. It is itself a container, and it is the entry point to the Kubernetes cluster. This is the process that all the different Kubernetes clients talk to: a UI (the Kubernetes dashboard, for instance), an API (scripts and automation tooling), and a command line tool. All three roads lead to the API server. Nothing reaches the cluster any other way.
Controller manager. It keeps an overview of what is happening in the cluster: whether something needs to be repaired, whether a container died and needs restarting, and so on. It is the process that notices reality has drifted from what you asked for.
Scheduler. It is responsible for scheduling containers onto different nodes based on workload and on the available server resources on each node. She stresses that this is an intelligent process, not a round robin: it decides which worker node the next container should land on by weighing the resources free on each worker against the load that container needs.
etcd. A key value store that holds, at any moment, the current state of the Kubernetes cluster. All the configuration data lives inside it, and all the status data of every node and every container inside those nodes. This is the cluster brain. And this is where the backup and restore promised earlier actually comes from: recovery is made from etcd snapshots, because you can recover the whole cluster state from one.
The virtual network, and why the master is the fragile part (7:09)
One more component, and she calls it very important: the virtual network that spans every node in the cluster. In simple words, the virtual network turns all the nodes inside a cluster into one powerful machine that has the sum of the resources of the individual nodes.
Two consequences follow from the split, and both are practical.
First, worker nodes are usually much bigger than the master. They carry most of the load because they run the applications, potentially hundreds of containers each, so they need the resources. The master runs a handful of master processes and does not need nearly as much.
Second, and pointing the other way, the master node is much more important than any individual worker. If you lose access to the master node, you cannot access the cluster at all any more. Which means you absolutely have to have a backup of your master at all times. In production environments you would run at least two masters inside your Kubernetes cluster, and in most cases more than two, so that when one master goes down the cluster continues to function smoothly on the others.
kubectl apply, enters through the API server and nowhere else, and every piece of state it decides on lands in etcd.What runs on every worker (5:05)
The master side is the interesting half architecturally, but the worker side is where your code lives, and it is three processes deep. The container runtime (Docker in this course, since Minikube ships with Docker packaged inside it) is what actually starts and stops containers. The kubelet, the one she names explicitly, is the Kubernetes agent on the node: it takes the pod specifications handed down from the API server, tells the runtime to start those containers, and reports back what is actually running. kube-proxy is the third, and it is the piece that makes the service abstraction later in this course real: it maintains the network rules on each node so that a request addressed to a service IP is forwarded to one of the pods behind it, wherever that pod happens to be running.
Together with the virtual network, that trio is why a pod on worker 1 can reach a pod on worker 2 by name and never think about which machine is which.
Node and pod: the smallest unit (9:29)
From here she stops drawing infrastructure and starts building an application, because the components make far more sense as answers than as definitions. The use case she builds is deliberately plain: a web application with a simple database. Every component from this point on enters because that application needs it.
Start with the basic setup of a worker node, or in Kubernetes terms just a node, which is a simple server, physical or virtual.
The basic component, and the smallest unit of Kubernetes, is a pod. A pod is an abstraction over a container. It creates a running environment, a layer on top of the container, and it exists for two reasons:
- Kubernetes wants to abstract away the container runtime. If the pod is the unit, the container technology underneath can be replaced without the rest of Kubernetes caring.
- You do not have to work with Docker directly. You interact with the Kubernetes layer, and Kubernetes talks to whatever container technology is installed.
So the application setup becomes an application pod, which is our own application, and a database pod with its own container.
An important convention lands here: a pod is usually meant to run one application container inside it. You can run multiple containers in one pod, but usually that only happens when you have one main application container plus a helper container or a side service that has to run alongside it.
She points out how unremarkable this looks so far: one server, two containers, an abstraction layer on top. The interesting part is what happens when they try to talk to each other.
Pods get IPs, and pods are ephemeral (10:45)
Kubernetes offers a virtual network out of the box, and on that network each pod gets its own IP address. Not the container, the pod. Each pod can communicate with every other pod using that address, which is internal, obviously not public. So the application container can reach the database using its IP.
Then the catch, and it is the one that generates the next four components:
Pod components in Kubernetes are ephemeral, which means that they can die very easily.
If the database container crashes, or the application inside it crashes, or the node runs out of resources, the pod dies. A new one gets created in its place, and the new pod gets a new IP address. Which is deeply inconvenient if you were addressing the database by IP, because now you have to adjust that address every time a pod restarts.
Service and ingress (12:19)
The fix for the moving IP is a service: a static, permanent IP address that gets attached to a pod. Your application gets its own service, the database pod gets its own service, and here is the property that makes it work:
The life cycles of service and the pod are not connected.
Even if the pod dies, the service and its IP stay. The endpoint never has to change again.
Next question: you want your application to be reachable from a browser, so you create an external service, a service that opens communication from external sources. But you would obviously not want your database open to public requests, so the database gets an internal service instead. Internal versus external is simply a type you specify when creating the service, which is exactly the type: NodePort line that appears in the demo later.
Except the URL an external service gives you is not very practical. What you get is the HTTP protocol, plus the IP address of the node (not the service), plus the port number of the service:
http://<node-ip>:<service-port>
Fine for test purposes when you want to check something fast, wrong for an end product. For a real product you want a secure protocol and a domain name:
https://my-app.com
That is what ingress is for. Instead of the request hitting the service directly, the request goes first to ingress, and ingress does the forwarding to the service.
She takes stock at this point, honestly: this is a very simple setup, one server, a couple of containers, some services, and nothing yet where the real advantages of Kubernetes come forward. They are coming, step by step.
ConfigMap and Secret (14:31)
Pods talk to each other through services, so the application has a database endpoint, let us say mongodb-service, that it uses to reach the database. Where does that endpoint normally live? In an application properties file, or as an external environment variable, but usually inside the built image of the application.
Which means that if the service name changes to, say, mongodb, you have to adjust that URL in the application, rebuild the application at a new version, push it to the repository, pull that new image into your pod, and restart the whole thing. All of that for a string.
So Kubernetes has a component called ConfigMap: your external configuration for your application. A ConfigMap holds configuration data such as the URLs of a database or of other services you use, and you connect it to the pod so that the pod receives the data the ConfigMap contains. Change the service endpoint later and you adjust the ConfigMap. That is the entire change. No new image, no cycle.
Part of the external configuration is also the database username and password, which can change during the deployment process too. But putting a password or other credentials into a ConfigMap in plain text would be insecure, even though it is external.
For that, Kubernetes has Secret. A Secret is just like a ConfigMap, with one difference: it is used to store secret data such as credentials, and it is stored not in plain text but in base64 encoded format.
She is careful, and correct, about what that does and does not buy you:
Of course, base64 encoding a secret doesn't make it automatically secure.
Secret components are meant to be encrypted using third party tools, because Kubernetes does not encrypt them out of the box. There are tools for this from cloud providers, and separate third party tools you can deploy into Kubernetes to encrypt your Secrets, and that is what makes Secrets actually secure.
What goes where: a database user could reasonably live in a ConfigMap, but passwords, certificates, anything you do not want other people to have access to, goes into a Secret. And just like a ConfigMap, you connect it to your pod so the pod can read from it. Both can be consumed the same two ways: as environment variables (which is what the demo does) or as a properties file mounted into the container.
Volume: data that survives a restart (17:52)
The database pod holds data, or generates data, and with the setup as it stands, if the database container or pod gets restarted, that data is gone. Obviously problematic, since you want database data and log data persisted reliably and long term.
The component for that is volumes. A volume attaches physical storage on a hard drive to your pod. That storage can be either:
- local, on the same server node where the pod is running, or
- remote, outside the Kubernetes cluster: cloud storage, or your own on premise storage that is not part of the cluster, referenced externally.
Now when the database pod or container restarts, the data is still there.
Then comes the sentence that matters more than the mechanism, and she says it twice in different words:
Think of storage as an external hard drive plugged into the Kubernetes cluster, because the point is Kubernetes cluster explicitly doesn't manage any data persistence.
Which means you, as the Kubernetes user or administrator, are responsible for backing the data up, replicating it, managing it, and making sure it is kept on proper hardware. Kubernetes does not take care of it. It is worth internalizing that on day one rather than after an incident.
Deployment and StatefulSet (19:46)
Everything is running, a user can reach the application through a browser. So what happens when the application pod dies, or you have to restart it because you built a new container image? You get downtime, a window where users cannot reach your application, which is a very bad thing in production.
This is exactly the advantage of distributed systems and containers. Instead of relying on one application pod and one database pod, replicate everything on multiple servers. Add a second node, run a replica of the application on it, and connect that replica to the same service.
Which reveals a second job the service was doing all along:
Service is also a load balancer, which means that the service will actually catch the request and forward it to whichever pod is least busy.
So a service is two things at once: a persistent static IP with a DNS name so endpoints stop moving, and a load balancer across the pods behind it.
But you do not create the second replica by hand. You define a blueprint for the application pod and specify how many replicas of it you want. That blueprint is a Deployment.
In practice you would not be working with pods, you would not be creating pods, you would be creating Deployments.
Because that is where you set the replica count, and where you scale up or down later. The abstraction stack is now two layers tall: a pod is a layer of abstraction on top of containers, and a Deployment is another layer of abstraction on top of pods, one that makes it convenient to replicate them and configure them. With a Deployment in place, when one replica of your application pod dies, the service forwards requests to another, and the application stays accessible.
Why the database cannot use a Deployment (22:04)
The natural next thought is: replicate the database the same way. If the database pod dies, the application is down regardless of how many application replicas exist.
But you cannot replicate a database with a Deployment, and the reason is state. The database has state, which is its data. If you clone it, all the replicas need to access the same shared data storage, and something has to manage which pods are currently writing to that storage and which are reading from it, in order to avoid data inconsistencies.
That mechanism, on top of the replication feature, is what another Kubernetes component provides: StatefulSet. It is meant specifically for applications like databases. MySQL, MongoDB, Elasticsearch, or any other stateful application should be created using StatefulSets and not Deployments. She calls it a very important distinction, and it is the one most beginners get wrong first.
A StatefulSet, just like a Deployment, takes care of replicating the pods and scaling them up or down, while additionally making sure database reads and writes are synchronized so no inconsistencies arise.
And then the caveat that saves people months, delivered without hedging:
Deploying database applications using StatefulSets in a Kubernetes cluster can be somewhat tedious, so it's definitely more difficult than working with Deployments.
Which is why it is common practice to host database applications outside the Kubernetes cluster entirely, keep only the stateless applications (which replicate and scale with no problem) inside the cluster, and have them talk to the external database. That is a legitimate architecture, not a cop out.
| Deployment | StatefulSet | |
|---|---|---|
| What it is for | Stateless applications: web apps, APIs, workers, anything that keeps nothing important on local disk | Stateful applications: MySQL, MongoDB, Elasticsearch, any database |
| Replication | Yes, set replicas and Kubernetes maintains that count | Yes, same idea |
| Scaling up and down | Yes, and it is trivial | Yes, but ordered and constrained by the data |
| Shared data storage | Not its problem, pods hold nothing worth keeping | Its whole problem: replicas share storage, so reads and writes must be synchronized |
| Pod identity | Interchangeable, any replica serves any request | Sticky, replicas are not interchangeable |
| Ease of use | easy, this is the default object you reach for | tedious, materially harder to operate correctly |
| Common escape hatch | None needed | Run the database outside the cluster and point the app at it |
With two replicas of the application pod and two replicas of the database, both load balanced, the setup is genuinely robust: if node 1 is rebooted or crashes outright and nothing can run on it, node 2 still has application and database pods on it, and the application stays accessible while the two lost replicas are recreated. That is how you avoid downtime, and it is the payoff for all of the previous abstractions.
The component recap (25:10)
She summarizes before moving to the hands on half, and the summary is worth keeping as a mental index.
| Component | What it is | The problem it solves | When you reach for it |
|---|---|---|---|
| Pod | An abstraction layer over a container, the smallest unit in Kubernetes, one main app container each | Decouples Kubernetes from the container technology, so you never talk to Docker directly | Almost never directly, you get pods from Deployments |
| Service | A permanent IP address plus DNS name in front of a set of pods, and a load balancer across them | Pods are ephemeral and get a new IP on every restart | Every application needs one, internal by default |
| Ingress | The entry point that routes external traffic into the cluster before it reaches a service | An external service gives you http://node-ip:port, which is not a product URL | When you want HTTPS and a real domain name |
| ConfigMap | External configuration in plain key value pairs, connected to a pod | Config baked into an image means a rebuild and redeploy for a URL change | Endpoints, hostnames, feature flags, anything non secret |
| Secret | The same idea, base64 encoded, meant to be encrypted by third party tooling | Credentials in a ConfigMap would sit in plain text | Passwords, certificates, tokens |
| Volume | Physical storage, local or remote, attached to a pod | Container restarts wipe everything the container wrote | Databases and logs, with backup remaining your job |
| Deployment | A pod blueprint with a replica count, an abstraction on top of pods | A single pod means downtime whenever it dies or is updated | Every stateless application, this is your default |
| StatefulSet | The same blueprint plus synchronized reads and writes and stable pod identity | Database replicas sharing storage would corrupt each other | Databases, if you insist on running them in cluster |
Just these core components, she notes, are enough to build genuinely powerful Kubernetes clusters.
Before the practical half she gives a shout out to the sponsor, Kasten, whose K10 is a data management platform for Kubernetes that takes most of the backup and restore burden off cluster administrators, with a simple UI and logic that does the heavy lifting.
Kubernetes configuration: everything is YAML sent to the API server (26:28)
So how do you actually create these components? All configuration in a Kubernetes cluster goes through the master node, specifically through the API server.
Kubernetes clients (a UI such as the Kubernetes dashboard, an API call which could be a script or a curl command, or a command line tool such as kubectl) all talk to the API server, which is the main entry point and the only entry point into the cluster. And the requests they send have to be in either YAML or JSON format.
The example she walks through is a Deployment: a template or blueprint for creating pods. It tells Kubernetes to create two replica pods called my-app, each pod replica running a container based on my-image, plus the environment variables and port configuration for that container inside the pod.
The critical property of these requests:
The configuration requests in Kubernetes are declarative. We declare what is our desired outcome from Kubernetes, and Kubernetes tries to meet those requirements.
Which is what turns configuration into self healing. Declare two replica pods of the my-app Deployment. One of those pods dies. The controller manager sees that the desired state and the actual state now differ, actual is 1 and desired is 2, so it goes to work restarting the second replica automatically until the declaration is true again.
Every configuration file has three parts (28:18)
Putting a Deployment and a Service configuration side by side, the structure is identical.
Part 1: metadata. Where the metadata of the component you are creating lives, most obviously its name.
Part 2: specification. Every component's configuration file has a spec section where you put every kind of configuration you want applied to that component. The attributes inside are specific to the kind of component: a Deployment has attributes that only apply to Deployments, a Service has its own.
Above both sits the pair of lines that declare what you are creating at all, apiVersion and kind, and she flags something that trips everyone up: the API version differs per component, and you have to look it up per component. It is apps/v1 for a Deployment and v1 for a Service, and there is no way to guess it.
Part 3: status. This one you never write. It is automatically generated and edited by Kubernetes. Kubernetes continuously compares the desired state against the actual state of the component, and if the status and the desired state do not match, it knows there is something to fix, and it tries to fix it.
This is the basis of the self healing feature that Kubernetes provides.
Concretely: you specify two replicas of an nginx Deployment. You apply the file, which is what "apply" means, creating the Deployment. Kubernetes adds the status of your deployment and updates it continuously. If the status at some point says only one replica is running, Kubernetes compares that status with the specification and knows another replica needs to be created.
Where the status data comes from (31:22)
An excellent question she raises and answers: where does Kubernetes get the status information it keeps writing back?
From etcd. The cluster brain, one of the master processes, holds the current status of any Kubernetes component at any time. That is the source of every status field you ever read.
Two practical notes on the files (31:54)
YAML is strict about indentation. The format itself is simple and pretty straightforward to read, but if something is indented wrongly, your file is invalid. That is the one syntax rule that will actually bite you.
Store the configuration files with your code. Since the Deployment and Service are applied to your application, it is good practice to keep these files in your application repository, usually as part of the infrastructure as code concept. Alternatively you can give the configuration files their own Git repository. Either is fine; leaving them on somebody's laptop is not.
Minikube and kubectl: a cluster on your laptop (32:39)
A production cluster looks like the architecture diagram: at least two masters, multiple worker nodes, separate responsibilities, and actual separate virtual or physical machines each representing a node. If you want to test something locally, or try out a new application or a new component quickly, standing that up is pretty difficult and possibly impossible on one laptop with finite memory and CPU.
Exactly for that use case there is an open source tool called Minikube.
Minikube is basically one node cluster where the master processes and the worker processes both run on one node.
That node comes with a Docker container runtime pre installed, so you can run containers, or pods with containers, on it immediately.
Now you have a virtual node on your local machine. You still need a way to interact with it, to create pods and other Kubernetes components on that node. That is kubectl, the command line tool for a Kubernetes cluster.
The path is the same as always: Minikube runs both master and worker processes, one of those master processes is the API server, the API server is the main entry point, and you talk to it through a client. There are three clients (a UI such as the dashboard, the Kubernetes API, and kubectl), and kubectl is the most powerful of the three, because with kubectl you can basically do anything in Kubernetes that you want. Once kubectl submits a command to the API server to create or delete components, the worker processes on the Minikube node actually make it happen.
One thing worth pinning down, because the name misleads people:
kubectl isn't just for a Minikube cluster. If you have a cloud cluster or a hybrid cluster, whatever, kubectl is the tool to use to interact with any type of Kubernetes cluster setup.
Minikube is the local cluster. kubectl is the universal client.
Installing Minikube (36:04)
There are many ways to install it depending on your operating system and its architecture, so the right move is to reference the official Minikube documentation, which lists the resource requirements and lets you select the correct options for your machine. Minikube can run either as a container or as a virtual machine, so check you have the resources before you start.
Her machine is macOS with Homebrew, so the install is one command:
# install Minikube (macOS with Homebrew; pick your own OS on the docs page)
brew install minikube
Watch the output as it runs. It installs dependencies for Minikube, and one of those dependencies is the Kubernetes CLI, which is kubectl. That is why she never installs kubectl separately: it arrives with Minikube.
Once installed, you create and start a Minikube cluster:
# start (and on first run, create) the local one node cluster
minikube start
The driver, and the two layers of Docker (37:04)
Minikube must start either as a container or as a virtual machine, so you need a container or virtual machine tool installed on your laptop for it to run inside. That tool is the driver. The drivers page lists what is supported on Linux, macOS, and Windows, and Docker is the preferred driver on all three operating systems.
This is the point she stops to clear up, because it confuses nearly everyone:
Minikube installation actually comes with Docker already installed to run those containers, but Docker as a driver for Minikube means that we are hosting Minikube on our local machine as a Docker container itself. So we have two layers of Docker.
Read it as a stack. Minikube runs as a Docker container on your machine. Inside Minikube, a separate Docker is packaged to run your application containers. The outer Docker hosts the cluster; the inner Docker runs your pods.
So if you already have Docker installed, you are ready. If not, install Docker Desktop from Docker Hub for Windows or Mac, drag and drop it into Applications, and start the Docker daemon so the whale is running before you continue.
With Docker running, start the cluster with Docker explicitly set as the driver:
# --driver=docker tells Minikube to host the cluster node as a Docker container
minikube start --driver=docker
The first run takes a while, because it has to create the cluster and download all the necessary images and components. Subsequent minikube start calls are much faster. When it finishes you have a local Kubernetes cluster on your machine (in the video, running Kubernetes 1.22, which was the latest at the time).
Check that everything inside came up:
# reports host, kubelet, apiserver and kubeconfig status for the local cluster
minikube status
All components should read as running and configured.
First contact with kubectl (40:09)
kubectl is already there as a Minikube dependency, so go straight to the cluster:
# list every node in the cluster
kubectl get node
You get exactly one node, which is the control plane and the worker node at the same time, along with its status, its Kubernetes version, and how long ago it joined the cluster.
That is the whole setup. From here on the division of labor is clean:
Minikube is basically just for the startup and for deleting the cluster, but everything else, configuring, we're going to be doing through kubectl.
The demo project: MongoDB plus a Node.js web app (41:17)
Now the knowledge gets spent. The target setup:
- deploy a MongoDB database into the cluster,
- deploy a web application that connects to MongoDB using external configuration data from a ConfigMap and a Secret,
- and make the web application accessible from a browser.
Two resources sit open beside the editor the whole time, and both are part of the method rather than decoration. The first is the Kubernetes documentation, which she copies the starting syntax of every component from, because that is the realistic way of working with Kubernetes: nobody memorizes the YAML. The second is Docker Hub, where the web application image she built is published publicly, so you can pull it into your own cluster exactly as she does.
Four configuration files go into a kubernetes-demo folder, in this order:
mongo-config.yaml, a ConfigMap holding the MongoDB database endpoint.mongo-secret.yaml, a Secret holding the MongoDB username and password.mongo.yaml, the MongoDB Deployment plus its internal Service.webapp.yaml, the web application Deployment plus its NodePort Service.
File 1: the ConfigMap (42:44)
Creating a ConfigMap is the simplest of the four. Copy the first example from the ConfigMap documentation, name it mongo-config, and fill in the data.
apiVersion: v1
kind: ConfigMap
metadata:
name: mongo-config
data:
mongo-url: mongo-service
Field by field:
apiVersion: v1, because ConfigMap is a core object. Remember that this differs per kind.kind: ConfigMap, the component being declared.metadata.name,mongo-config. This is the name other manifests will reference, so it is not cosmetic.data, the actual contents: all the key value pairs you are defining as external configuration. Here there is exactly one.mongo-url: mongo-service. The key ismongo-url; the value ismongo-service, which is the name of the MongoDB Service that does not exist yet. That is the trick worth noticing: inside a cluster, a service name is a resolvable hostname, so the database endpoint is literally the string you will type asmetadata.nameon the Service two files from now. The two have to match exactly.
File 2: the Secret (44:17)
Copy the example from the Secret documentation, and take the whole thing this time, because there is one extra field.
apiVersion: v1
kind: Secret
metadata:
name: mongo-secret
type: Opaque
data:
mongo-user: bW9uZ291c2Vy
mongo-password: bW9uZ29wYXNzd29yZA==
What changed relative to the ConfigMap:
kind: Secretinstead of ConfigMap.type: Opaque, which is the generic type for arbitrary secret data. Kubernetes has other Secret types for specific purposes (docker registry credentials, TLS certificates), butOpaqueis the everyday one.dataworks the same way, except the values must be base64 encoded. You cannot paste plain text here.
Encoding is one command per value:
# -n matters: without it, echo appends a newline and you encode a value with a
# trailing \n in it, which is a classic cause of "authentication failed"
echo -n mongouser | base64
# bW9uZ291c2Vy
echo -n mongopassword | base64
# bW9uZ29wYXNzd29yZA==
Paste each result as the value. To go the other way and check what a Secret actually holds, base64 --decode reverses it:
echo -n bW9uZ291c2Vy | base64 --decode
# mongouser
Keep in mind what was said at 16:27: this is encoding, not encryption. Anyone who can read the Secret can read the password. Encrypting Secrets properly is a third party tool's job.
File 3: the MongoDB Deployment (45:49)
Third file, mongo.yaml, and this one holds both the Deployment and the Service. You can keep them in separate files, but it is a very common thing to put them together, because every Deployment needs a Service, so grouping them keeps the pair in one place.
Grab the Deployment example from the documentation and adjust it. It looks more complex than the ConfigMap or the Secret, so she walks every attribute.
The core of it is the template, which is the pod blueprint:
Template basically is a configuration of the pod within the configuration of deployment.
And the template has its own metadata and its own spec, exactly like the Deployment does, one level down. That nesting is the single most confusing thing about a Deployment manifest the first time you see it, and once you see that it is a pod manifest embedded inside a Deployment manifest, it stops being confusing.
Inside the pod's spec sits containers, a list, because a pod can hold multiple containers (though mostly one main application container). For each container you set:
image, heremongoat tag5.0, which she picks off the tags section on Docker Hub.name,mongodb, just the name of the container.ports.containerPort, the port the container listens on. Checking the image documentation, MongoDB starts on 27017, so that is the value.
That much configures the Deployment to create pods running a MongoDB 5.0 image. Now the other stuff.
Labels and selectors (48:54)
Two attributes need explaining: labels in the metadata section, and matchLabels in the selector.
In Kubernetes you can give any component a key value pair label. Pods, Deployments, ConfigMaps, anything. Labels are additional identifiers of components, on top of the name, so you can identify and address specific components by label.
Why you need them: when you have multiple replicas of the same pod, each pod gets a unique name, but they can all share the same label. So a label is how you identify every pod replica of one application at once. That is why the pod metadata always carries a label.
For pods, labels is a required field. For other components like Deployment, ConfigMap and so on, labels is optional, but it is a good practice to set them.
Then the connection: how does a Deployment know which pods belong to it? Through selector.matchLabels in the Deployment's spec. It says: every pod that matches this label belongs to this Deployment. The selector matches the pods created by this configuration because those pods carry that label in their template metadata.
Are the labels themselves prescribed? No:
These are totally up to you. You can call it whatever you want, you can call it
my-key: my-value, it doesn't really matter.
But the standard and common practice in Kubernetes is to use app as the key when labeling applications, with the application name as the value. So the nginx placeholders from the docs example become app: mongo, in all three places: the Deployment's own labels, the selector's matchLabels, and the pod template's labels. The Deployment name becomes mongo-deployment.
Replicas (51:32)
The last Deployment attribute is replicas, which is as simple as it sounds: how many pods to create from this blueprint. Here it is 1, and there is a reason:
It's a database, and as you learned, if you want to scale databases in Kubernetes you should use StatefulSet and not a Deployment.
So to keep everything simple, MongoDB stays at one replica. The lesson from 22:04 is being obeyed rather than quietly ignored.
Here is the Deployment half of mongo.yaml as it stands at this point, before the environment variables are added at 56:43:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mongo-deployment
labels:
app: mongo
spec:
replicas: 1
selector:
matchLabels:
app: mongo
template:
metadata:
labels:
app: mongo
spec:
containers:
- name: mongodb
image: mongo:5.0
ports:
- containerPort: 27017
The MongoDB Service, in the same file (52:04)
Every application needs a Service, so the Service goes into the same YAML file as a separate document, separated by three dashes:
---
That is basic YAML syntax for "a second document starts here", nothing specific to Kubernetes, and it is why one file can hold two objects.
Service configuration is much easier than a Deployment. Grab the Service example and adjust:
---
apiVersion: v1
kind: Service
metadata:
name: mongo-service
spec:
selector:
app: mongo
ports:
- protocol: TCP
port: 27017
targetPort: 27017
The fields:
metadata.name: mongo-service. This is the endpoint, and this is exactly the value written into the ConfigMap in file 1. If these two strings disagree, the web app cannot find the database, and nothing else in the setup will tell you why.spec.selector. A Service needs to forward the requests it receives to its endpoint pods, so how does it know which pods belong to it? The same label selector mechanism as the Deployment.app: mongohere must match the label on the pods. That is how services and pods find each other.ports.port. A Service is accessible inside the cluster at its own IP address and this port. It can be anything you decide on: 80, 8080, it does not really matter.ports.targetPort. The port of the pods behind the Service. Logically,targetPortmust equal the container port, because that is where the application inside the pod is actually listening, so that is where the Service has to forward.
Said once more, because these two are the pair people mix up: port sets the port of the Service; targetPort tells the Service which port to forward to on the pods. They can legitimately differ, but the common standard is to set them the same just to keep things simple, which is why both read 27017 here.
Note also what is not in this Service: a type. Leave it out and you get the default, ClusterIP, an internal service, which is precisely right for a database that should never be reachable from outside the cluster.
File 4: the web application (54:36)
The fourth file, webapp.yaml, starts as a copy of the whole of mongo.yaml, with the values adjusted. That is not laziness, it is the shape of the thing: Deployment plus Service is the basic configuration for any application in a Kubernetes cluster.
What changes:
- Every name and label becomes
webapp:webapp-deployment,webapp-service,app: webappin the Deployment labels, inselector.matchLabels, and in the pod template labels. - The image becomes her published demo image from Docker Hub,
nanajanashia/k8s-demo-appat tagv1.0, which is publicly accessible, so you can pull it too. - The application is a very simple Node.js application that starts on port 3000, so
containerPortis 3000,targetPorton the Service is 3000 to match, and the Serviceportis set to 3000 as well for consistency.
Wiring the ConfigMap and Secret into the containers (56:11)
Both Deployments still need one thing: the data defined in the ConfigMap and Secret components has to reach the containers.
Start with MongoDB. When a MongoDB container starts it will create a root username and password from environment variables, and you then use those credentials to reach it from inside the cluster. How do you know which variables? You read the image documentation on Docker Hub, which names them. They are MONGO_INITDB_ROOT_USERNAME and MONGO_INITDB_ROOT_PASSWORD, and she notes these are effectively required fields in most databases: without them you will not be able to access the thing at all.
So the next question is how to pass environment variables to an application running inside a container. Also easy: an env attribute on the container, holding a list of environment variables with names and values.
env:
- name: MONGO_INITDB_ROOT_USERNAME
value: mongouser
name is the environment variable name, value is its value. You could hardcode the username right there like that. But the whole point of files 1 and 2 was to avoid it, so instead you reference the Secret with valueFrom and secretKeyRef:
env:
- name: MONGO_INITDB_ROOT_USERNAME
valueFrom:
secretKeyRef:
name: mongo-secret
key: mongo-user
- name: MONGO_INITDB_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mongo-secret
key: mongo-password
Read it as a two step lookup: name is the name of the Secret component (mongo-secret, from metadata.name in file 2) and key is the key inside that Secret's data block. Kubernetes finds the Secret with that name, gets the value stored under that key, and substitutes it as the value of the environment variable. The password is now a reference, never a literal, in the manifest you commit to Git.
With that, the MongoDB configuration file is complete, and when the pod starts, a user with those credentials is created.
The web app needs three values (59:16)
When the web application starts it must connect to the database, so it needs three things: where the database is, and which username and password to authenticate with. She has already built the application to expect all three as environment variables with specific names, so the Deployment passes them in:
env:
- name: USER_NAME
valueFrom:
secretKeyRef:
name: mongo-secret
key: mongo-user
- name: USER_PWD
valueFrom:
secretKeyRef:
name: mongo-secret
key: mongo-password
- name: DB_URL
valueFrom:
configMapKeyRef:
name: mongo-config
key: mongo-url
Two things to take from this block.
The first two are copy paste of the MongoDB block, and she calls that out as the visible payoff of external configuration:
You already see an advantage of using configuration from Secret or ConfigMap, because if you need the same information in 10 different applications, you create it once and reference it 10 times.
Note also that the environment variable names differ (USER_NAME and USER_PWD here, MONGO_INITDB_ROOT_USERNAME and MONGO_INITDB_ROOT_PASSWORD there) while the underlying Secret keys are identical. The variable name is whatever the consuming application demands; the key is where the value lives. Those are two different namespaces.
The third one comes from the ConfigMap, and the syntax is deliberately parallel: same valueFrom, but configMapKeyRef instead of secretKeyRef, then the same name (the ConfigMap mongo-config) and key (mongo-url) pair. The value it resolves to is mongo-service, which is the DNS name of the MongoDB Service, which routes to the MongoDB pod.
The result:
We don't have any of the configuration values hardcoded in our Kubernetes configuration files, we only have references, which makes our configuration way cleaner. So if something changes, or the values change here, we don't have to adjust anything in our Deployments.
Making the web app reachable: NodePort (1:02:22)
Connectivity to the database is configured. One thing is still missing before deploying: you want to type a URL into a browser and see the application.
As covered at 12:51, external services exist for that. Right now both Service configurations are internal, because neither one specifies a type, and the default type is ClusterIP. To make the web app service external, set the type to NodePort:
spec:
type: NodePort
NodePort is an external service type, and it requires a third port, called nodePort:
This is a port which will open on the Kubernetes nodes, on which the application will be accessible.
So the combination of the node IP address and the nodePort reaches the Service, which then reaches the pods behind it. Three ports now stack up in a NodePort Service, and they are all different jobs:
| Field | Where it lives | What it is | Value here |
|---|---|---|---|
nodePort | On the node, open to the outside world | The port you hit from your browser, at the node's IP | 30100 |
port | On the Service, inside the cluster | The port the Service itself listens on for in cluster traffic | 3000 |
targetPort | On the pods behind the Service | Where the Service forwards to, must equal containerPort | 3000 |
And nodePort is not a free choice. The range is defined by Kubernetes:
It has to be within the range of 30000 and 32767.
Anything in that range is fine, so 30000 or 30100, it does not really matter. She uses 30100, and that completes the web app configuration file. What is on disk now is a simple but genuinely realistic configuration: an application, its database, and external configuration, all in the cluster.
Applying everything, in dependency order (1:03:54)
The Minikube cluster is already running but has no components in it. Order matters here, and it is worth stating why rather than just following along.
The external configuration goes first, because the MongoDB and web app Deployments reference the ConfigMap and Secret, so those have to exist by the time the Deployments are created. Then the database, because the web application depends on it and should find it already up. Then the web app.
The command is kubectl apply with -f, which stands for file, taking a Kubernetes configuration file as input and creating whatever is defined inside it:
# 1. external configuration first: the Deployments reference these by name
kubectl apply -f mongo-config.yaml
# configmap/mongo-config created
kubectl apply -f mongo-secret.yaml
# secret/mongo-secret created
# 2. the database next: the web app depends on it
kubectl apply -f mongo.yaml
# deployment.apps/mongo-deployment created
# service/mongo-service created
# 3. finally the web application
kubectl apply -f webapp.yaml
# deployment.apps/webapp-deployment created
# service/webapp-service created
Notice that the third and fourth commands each report two objects created, because each of those files holds two YAML documents separated by ---.
mongo-service, which is a literal string stored in a ConfigMap and injected as DB_URL. Change the Service name and you change one line of the ConfigMap, not the application image.Interacting with the cluster (1:05:40)
Everything applied cleanly, but "seems fine" is not a state. Check what actually exists.
# every component created in the cluster: deployments, the pods behind them,
# the ReplicaSets in between, and all the services
kubectl get all
The output shows mongo-deployment and webapp-deployment, each with one replica running, the pods behind them, and both services, with webapp-service listed as type NodePort, which is what tells you it is reachable externally.
What kubectl get all does not show is the ConfigMap and the Secret. Those need their own commands:
kubectl get configmap
kubectl get secret
The general shape she draws out of that:
Displaying any component is pretty easy using kubectl. You just do
kubectl getand the name of the component, like pod, and you get a list of those components with some additional data.
kubectl get pod
kubectl get service
kubectl get deployment
kubectl get node
kubectl documents itself (1:06:28)
kubectl is a very powerful tool with a lot of subcommands, and the natural documentation for it is the tool itself:
# lists every subcommand available
kubectl help
# help for one subcommand, with examples and every available option
kubectl get --help
That second form is the useful one in practice: per subcommand help gives you the examples plus the full option list, so you can navigate what is available without leaving the terminal. The online kubectl reference covers the same ground with more context.
describe: the detail view (1:07:30)
kubectl get lists. When you want more detail about one specific component, kubectl describe takes the component type and then the instance name:
# detailed output for one service
kubectl describe service webapp-service
# detailed output for one pod: how it was scheduled, container config, labels,
# and the event log, which is where you find out why a pod will not start
kubectl describe pod webapp-deployment-<hash>
For a pod that gives you the status of how the pod was scheduled, the container configuration, the labels, and more. It is the first command to reach for when something is not running and you do not know why.
logs: what the container is saying (1:08:02)
When applications are running in your cluster you want to check the logs, to troubleshoot, to debug, or just to confirm everything is fine inside the pod.
# the logs of the container inside that pod
kubectl logs webapp-deployment-<hash>
# -f follows, streaming new log lines as they arrive
kubectl logs -f webapp-deployment-<hash>
The pod name is the long generated one you copy out of kubectl get pod, made of the Deployment name plus the ReplicaSet hash plus the pod hash. That naming is exactly the reason labels exist: names are unique per pod and therefore unmemorable, labels are shared and therefore addressable.
A close relative worth knowing, since a shell inside a container answers questions logs cannot:
# open an interactive shell inside the running container
kubectl exec -it webapp-deployment-<hash> -- /bin/bash
And the command that undoes an apply, which is the other half of the workflow:
# delete by file, removing exactly what that file created
kubectl delete -f webapp.yaml
# or delete one named object
kubectl delete deployment webapp-deployment
Opening it in a browser (1:08:34)
The last step is validating that the application really is reachable from a browser. Get the service:
# svc is the accepted short form of service
kubectl get service
kubectl get svc
That gives you the port. But which IP address goes in front of it? The rule for NodePort:
The NodePort service is always accessible at the IP address of the cluster node.
All the worker nodes the cluster has, and here there is exactly one, the Minikube node. So you need the Minikube node's IP:
minikube ip
Or from the other direction, with kubectl, using the wide output format:
# -o wide prints extra columns, including the node's internal IP
kubectl get node -o wide
Both produce the same address. And -o wide is not special to nodes:
You can use the
-o wideoption for any othergetcommand, for services, pods and so on, to get some additional information.
kubectl get pod -o wide
kubectl get svc -o wide
Now put the two halves together and open it:
http://<minikube-ip>:30100
There it is: the web application, connected to MongoDB. And she validates the connection rather than assuming it, which is the right instinct: edit something in the app and save it, because that request goes to the database, then refresh the page and confirm the change is still there. Data survived a round trip through the pod, the internal Service, and Mongo. The wiring is real.
What you just built (1:10:39)
So we deployed an application with its database in Kubernetes, which is a blueprint configuration for most common application setups you're gonna have.
That is the honest claim about this demo: it is small, but the shape (a stateless application, a database, external configuration, an internal service, one external entry point) is the shape of most real deployments. You also collected a working set of kubectl commands, and, just as important, the habit of referencing the official Kubernetes documentation to configure and create components rather than trying to remember YAML.
She closes by pointing at the two next steps she maintains: the Kubernetes Administrator course if you want to build and administer clusters from scratch and sit the CKA, and the complete DevOps educational program, a six month program covering all the concepts and technologies, Kubernetes included, needed to get started in DevOps or cloud engineering.
The complete manifests
All four files, in the order she creates them and in the order you apply them. Copy them into a folder, run the four kubectl apply commands, and you have the same setup.
mongo-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mongo-config
data:
mongo-url: mongo-service
mongo-secret.yaml (values are echo -n mongouser | base64 and echo -n mongopassword | base64; encode your own before using this anywhere real)
apiVersion: v1
kind: Secret
metadata:
name: mongo-secret
type: Opaque
data:
mongo-user: bW9uZ291c2Vy
mongo-password: bW9uZ29wYXNzd29yZA==
mongo.yaml (Deployment and internal Service in one file, split by ---)
apiVersion: apps/v1
kind: Deployment
metadata:
name: mongo-deployment
labels:
app: mongo
spec:
replicas: 1
selector:
matchLabels:
app: mongo
template:
metadata:
labels:
app: mongo
spec:
containers:
- name: mongodb
image: mongo:5.0
ports:
- containerPort: 27017
env:
- name: MONGO_INITDB_ROOT_USERNAME
valueFrom:
secretKeyRef:
name: mongo-secret
key: mongo-user
- name: MONGO_INITDB_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mongo-secret
key: mongo-password
---
apiVersion: v1
kind: Service
metadata:
name: mongo-service
spec:
selector:
app: mongo
ports:
- protocol: TCP
port: 27017
targetPort: 27017
webapp.yaml (Deployment and external NodePort Service)
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp-deployment
labels:
app: webapp
spec:
replicas: 1
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- name: webapp
image: nanajanashia/k8s-demo-app:v1.0
ports:
- containerPort: 3000
env:
- name: USER_NAME
valueFrom:
secretKeyRef:
name: mongo-secret
key: mongo-user
- name: USER_PWD
valueFrom:
secretKeyRef:
name: mongo-secret
key: mongo-password
- name: DB_URL
valueFrom:
configMapKeyRef:
name: mongo-config
key: mongo-url
---
apiVersion: v1
kind: Service
metadata:
name: webapp-service
spec:
type: NodePort
selector:
app: webapp
ports:
- protocol: TCP
port: 3000
targetPort: 3000
nodePort: 30100
The full run, start to browser
minikube start --driver=docker
minikube status
kubectl apply -f mongo-config.yaml
kubectl apply -f mongo-secret.yaml
kubectl apply -f mongo.yaml
kubectl apply -f webapp.yaml
kubectl get all
kubectl get configmap
kubectl get secret
minikube ip # then open http://<that-ip>:30100 in a browser
Key takeaways
- Container orchestration is a consequence, not a fashion. Microservices drove container adoption, container adoption produced applications made of hundreds or thousands of containers, and managing those with scripts became impossible. Kubernetes exists to guarantee three things: high availability, scalability, and disaster recovery.
- There is exactly one door into a cluster. Every client, dashboard, script, or kubectl, sends YAML or JSON to the API server on the master node. The scheduler places work, the controller manager repairs drift, and etcd holds the state that all of it is compared against, which is also what you snapshot for backup.
- The master is small and irreplaceable; the workers are big and disposable. Lose a worker and pods get rescheduled. Lose your only master and you have lost access to the cluster, which is why production runs at least two.
- Every component in the course answers a problem the previous one created. Pods die and change IP, so you add a Service. The service URL is ugly, so you add ingress. Config is baked into the image, so you add a ConfigMap. Credentials in plain text are bad, so you add a Secret. Restarts erase data, so you add a volume. A single pod means downtime, so you add a Deployment. A replicated database corrupts itself, so you add a StatefulSet.
- Deployments for stateless, StatefulSets for stateful, and running the database outside the cluster is a respectable third answer. She says plainly that StatefulSets are tedious and that hosting databases outside Kubernetes is common practice.
- Kubernetes is declarative, and that is what self healing means. You describe the desired state; the control plane continuously compares it to the actual state pulled from etcd and works to close the gap. Nobody restarts anything by hand.
- Labels are the glue. Names are unique per pod and therefore useless for addressing groups; labels are shared.
selector.matchLabelsis how a Deployment claims its pods, and how a Service finds the pods to forward to. Useapp: <name>because it is the convention, and make it match in all three places. - Learn the three ports.
containerPortis where the application listens,targetPortmust equal it,portis the Service's own port inside the cluster, andnodePort(30000 to 32767 only) is the door in from outside. - Reference the documentation, do not memorize YAML. She copies every manifest skeleton from the Kubernetes docs on camera, on purpose, because that is the real workflow.
Chapters
- 0:00 Intro and Course Overview
- 1:44 What is Kubernetes
- 4:33 Kubernetes Architecture
- 9:29 Node & Pod
- 12:19 Service & Ingress
- 14:31 ConfigMap & Secret
- 17:52 Volume
- 19:46 Deployment & StatefulSet
- 26:28 Kubernetes Configuration
- 32:39 Minikube and Kubectl - Setup K8s cluster locally
- 41:17 Complete Demo Project: Deploy WebApp with MongoDB
- 1:05:40 Interacting with Kubernetes Cluster
- 1:11:03 Congrats! You made it to the end
Notable quotes
"Kubernetes is an open source container orchestration framework which was originally developed by Google." (1:44)
"Managing those loads of containers across multiple environments using scripts and self made tools can be really complex and sometimes even impossible." (3:02)
"Kubelet is actually a Kubernetes process that makes it possible for the cluster to talk to each other, to communicate to each other, and actually execute some tasks on those nodes." (4:33)
"An API server is actually the entry point to the Kubernetes cluster." (5:37)
"etcd key value storage basically holds at any time the current state of the Kubernetes cluster. The backup and restore that we mentioned previously is actually made from these etcd snapshots." (6:37)
"If you lose a master node access, you will not be able to access the cluster anymore." (8:11)
"Pod components in Kubernetes are ephemeral, which means that they can die very easily." (11:16)
"The life cycles of service and the pod are not connected, so even if the pod dies the service and its IP address will stay." (12:21)
"Of course, base64 encoding a secret doesn't make it automatically secure. The secret components are meant to be encrypted using third party tools." (16:27)
"Think of a storage as an external hard drive plugged in into the Kubernetes cluster, because the point is Kubernetes cluster explicitly doesn't manage any data persistence." (19:01)
"Service is also a load balancer, which means that the service will actually catch the request and forward it to whichever pod is least busy." (20:32)
"In practice you would not be working with pods, you would not be creating pods, you would be creating deployments." (21:33)
"Deploying database applications using StatefulSets in Kubernetes cluster can be somewhat tedious, so it's definitely more difficult than working with deployments. That's why it's also a common practice to host database applications outside of the Kubernetes cluster." (23:39)
"The configuration requests in Kubernetes are declarative form, so we declare what is our desired outcome from Kubernetes and Kubernetes tries to meet those requirements." (27:46)
"This is the basis of the self healing feature that Kubernetes provides." (30:20)
"YAML is very strict about the indentations, so for example if you have something wrongly indented here, your file will be invalid." (31:54)
"Minikube is basically one node cluster where the master processes and the worker processes both run on one node." (33:27)
"kubectl isn't just for Minikube cluster. If you have a cloud cluster or a hybrid cluster, whatever, kubectl is the tool to use to interact with any type of Kubernetes cluster setup." (35:31)
"For pods, labels is a required field. For other components like deployment, ConfigMap etc, labels is optional, but it is a good practice to set them." (49:26)
"Port attribute sets the port of the service, and target port tells service to which port it should forward the request to the pods." (54:06)
"If you need the same information in 10 different applications, you create it once and reference it 10 times." (1:00:16)
"Node port range is actually defined in Kubernetes, so we can't just type anything we want. It has to be within the range of 30000 and 32767." (1:03:24)
"We deployed an application with its database in Kubernetes, which is a blueprint configuration for most common application setups you're gonna have." (1:10:39)
Resources mentioned
The core tooling
- Kubernetes, the container orchestration framework the whole course is about
- Kubernetes documentation, which she references live on camera to build every manifest
- ConfigMap documentation, the source of file 1
- Secret documentation, the source of file 2
- Deployment documentation, the source of the Deployment blocks in files 3 and 4
- Service documentation, the source of both Service blocks
- StatefulSet documentation, for the stateful half of the Deployment versus StatefulSet distinction
- Ingress documentation, for the HTTPS and domain name step past NodePort
- Volumes documentation, for persistent storage attached to pods
- etcd, the key value store that holds cluster state and is what you snapshot for backup
- kubectl reference, the online version of
kubectl help - kubectl cheat sheet, for the
get,describe,logs,apply,deleteset used here - Minikube, the one node local cluster
- Minikube start guide, the install page with the per OS instructions and resource requirements
- Minikube drivers page, the list of supported drivers where Docker is preferred on all three operating systems
Containers and images
- Docker, the container technology used as both the Minikube driver and the runtime inside it
- Docker Desktop, the install she walks through on macOS
- Docker Hub, where both images in the demo are pulled from
- mongo on Docker Hub, the official MongoDB image, tag
5.0, port 27017, and the documentation forMONGO_INITDB_ROOT_USERNAMEandMONGO_INITDB_ROOT_PASSWORD - nanajanashia/k8s-demo-app, her publicly accessible demo web application image, tag
v1.0, port 3000 - MongoDB, the database in the demo
- Node.js, what the demo web application is written in
- Homebrew, the package manager used for
brew install minikubeon macOS - Visual Studio Code, the editor the four YAML files are written in
- YAML, the configuration format, with its strict indentation rules
People, courses, and the sponsor
- TechWorld with Nana on YouTube, Nana Janashia's channel
- techworld-with-nana.com, her site
- Complete Kubernetes Administrator course, building, configuring and managing clusters from scratch, aimed at the CKA
- Complete DevOps educational program, the six month program she points at for aspiring DevOps and cloud engineers
- Certified Kubernetes Administrator (CKA), the certification the administrator course targets
- Linux Foundation, which administers the CKA
- Cloud Native Computing Foundation, the home of Kubernetes since Google donated it
- Kasten and Kasten K10, the sponsor, a data management platform that handles backup and restore for Kubernetes clusters
Where it stands
The course was published in September 2021 against Kubernetes 1.22, and the concepts in it have aged well, which is the nature of a design that has not moved much. Pods, services, ingress, ConfigMaps, Secrets, volumes, Deployments, StatefulSets, and the control plane split are all exactly as described. Every manifest on this page still applies cleanly on a current cluster, and apps/v1 and v1 are still the right API versions for these objects.
Four things have shifted underneath the vocabulary since, and they are worth knowing before you take this into a modern cluster.
"Master node" is now "control plane node". The Kubernetes project moved off the master and slave terminology; the kubectl get node output she reads on screen already says control-plane for exactly that reason. The architecture is unchanged, the word is not.
Docker is no longer the container runtime inside Kubernetes. Kubernetes removed the dockershim in version 1.24, released in 2022, so clusters now run containerd or CRI-O as the runtime. This does not affect anything on this page: your images are still built by Docker and are still OCI images, Docker remains the preferred Minikube driver, and pods run identically. It only changes what is running on the worker under the kubelet, which is precisely the abstraction the pod exists to hide.
Ingress has a successor in progress. The Gateway API is the project's newer, more expressive answer to routing external traffic, and it is where new work is going. Ingress is not going away and remains the thing to learn first, but on a greenfield cluster in 2026, Gateway API deserves a look.
Secrets deserve one more layer than she had room for. Her warning at 16:27 is still exactly right and still routinely ignored. Modern clusters back it up with encryption of Secret data at rest in etcd, and Git friendly workflows tend to use Sealed Secrets or the External Secrets Operator so that a base64 blob never lands in a repository at all. If you take one habit from this course into production, take that one.


