Introducing Pods (and other Kubernetes objects)
One of the biggest challenges for implementing cloud native technologies is learning the fundamentals—especially when you need to fit your learning into a busy schedule. In this series, we’ll break down core cloud native concepts, challenges, and best practices into short, manageable exercises and explainers, so you can learn five minutes at a time.
In the last lesson, we investigated the architecture of a Kubernetes cluster, including the major components of the control plane and worker nodes, as well as common architectural patterns for a cluster. Today, we’ll start to break down the core abstractions that organize activity on the cluster—starting with Pods.
Table of Contents
Introducing Pods (and other Kubernetes objects) ← You are here
How to Use Kubernetes Secrets with Environment Variables and Volume Mounts
How to Use StatefulSets and Create a Scalable MySQL Server on Kubernetes
These lessons assume a basic understanding of containers. If you need to get up to speed on Docker and containerization, download our free ebook, Learn Containers 5 Minutes at a Time. This concise, hands-on primer explains:
- The key concepts underlying containers—and how to use core tools like image registries
- Fundamentals of container networking that are essential for understanding container orchestrators like Kubernetes
- How to deploy containerized apps in single-container and multi-container configurations
What are Kubernetes objects?
Kubernetes defines entities on the cluster as objects. The term functions the same way here as in most computer science contexts: Kubernetes objects are instances of a class with certain attributes, and they give us a model for abstraction. In Kubernetes, we’re using objects to define entities in our cluster—and more specifically, the desired state and status of those entities. Kubernetes is all about intent, and objects are the model we use to express our intent.
So far we’ve been a little vague—what kind of “entities” are we talking about here, and what might we intend for them? Well, let’s have a look. We can use kubectl
to retrieve a complete list of abstractions that are manageable through the Kubernetes API. Start Minikube and then enter:
% kubectl api-resources
You should see a list something like this:
NAME SHORTNAMES APIVERSION
bindings v1
componentstatuses cs v1
configmaps cm v1
endpoints ep v1
events ev v1
limitranges limits v1
namespaces ns v1
nodes no v1
persistentvolumeclaims pvc v1
persistentvolumes pv v1
pods po v1
podtemplates v1
replicationcontrollers rc v1
resourcequotas quota v1
secrets v1
serviceaccounts sa v1
services svc v1
mutatingwebhookconfigurations admissionregistration.k8s.io/v1
validatingwebhookconfigurations admissionregistration.k8s.io/v1
customresourcedefinitions crd,crds apiextensions.k8s.io/v1
apiservices apiregistration.k8s.io/v1
controllerrevisions apps/v1
daemonsets ds apps/v1
deployments deploy apps/v1
replicasets rs apps/v1
statefulsets sts apps/v1
tokenreviews authentication.k8s.io/v1
localsubjectaccessreviews authorization.k8s.io/v1
selfsubjectaccessreviews authorization.k8s.io/v1
selfsubjectrulesreviews authorization.k8s.io/v1
subjectaccessreviews authorization.k8s.io/v1
horizontalpodautoscalers hpa autoscaling/v2
cronjobs cj batch/v1
jobs batch/v1
certificatesigningrequests csr certificates.k8s.io/v1
leases coordination.k8s.io/v1
endpointslices discovery.k8s.io/v1
events ev events.k8s.io/v1
flowschemas flowcontrol.apiserver.k8s.io/v1beta2
prioritylevelconfigurations flowcontrol.apiserver.k8s.io/v1beta2
nodes metrics.k8s.io/v1beta1
pods metrics.k8s.io/v1beta1
ingressclasses networking.k8s.io/v1
ingresses ing networking.k8s.io/v1
networkpolicies netpol networking.k8s.io/v1
runtimeclasses node.k8s.io/v1
poddisruptionbudgets pdb policy/v1
podsecuritypolicies psp policy/v1beta1
clusterrolebindings rbac.authorization.k8s.io/v1
clusterroles rbac.authorization.k8s.io/v1
rolebindings rbac.authorization.k8s.io/v1
roles rbac.authorization.k8s.io/v1
priorityclasses pc scheduling.k8s.io/v1
csidrivers storage.k8s.io/v1
csinodes storage.k8s.io/v1
csistoragecapacities storage.k8s.io/v1beta1
storageclasses sc storage.k8s.io/v1
volumeattachments storage.k8s.io/v1
Read through the complete list; some concepts like “nodes'' should be familiar already. (We’ve removed the NAMESPACED and KIND columns for readability, but those are informative—review them, too!)
Kubernetes uses these abstractions to record our intent for system resources in a way that the API server can use in communicating with other components. A resource might have to do with workloads, services, policies, or another area of the system. These are essentially the classes that Kubernetes objects instantiate, and objects give us a common model for all of these resources.
In this series, we’ll focus on the most essential Kubernetes objects for developers. Let’s start with the elementary particle of Kubernetes: the Pod.
What is a Pod?
We can ask Kubernetes itself. In the terminal, enter:
% kubectl explain pod
You should get the following answer:
KIND: Pod
VERSION: v1
DESCRIPTION:
Pod is a collection of containers that can run on a host. This resource is created by clients and scheduled onto hosts.
…
So what does this mean? What is a Pod? A Pod is a unit of one or more containers that share a network namespace and storage volumes while running on a Kubernetes node. It is also Kubernetes’ most granular unit of scheduling for a containerized workload.
Up until now, we’ve used the general term “workload” to refer to the containerized processes that Kubernetes is orchestrating. As we’ve seen previously, the system assigns these workloads to worker nodes, the physical or virtual machines that are part of the cluster. These are the “hosts” in the definition above. But Kubernetes adds a conceptual layer separating the node from the container, and that’s the Pod.
Each Pod is assigned a unique IP address, so containers within the Pod can communicate with one another over localhost, and Pods can communicate with one another at fixed addresses.
These two ideas—the abstraction of the Pod, and each Pod having its own IP address—are fundamental, defining concepts for Kubernetes. They extend all the way back to the Borg project at Google, and they distinguish Kubernetes from other container orchestrators like Swarm. It is difficult to overstate the importance of the Pod; it contributes to the high scalability of Kubernetes, and it informs many of the networking patterns we will encounter going forward.
We’ve said that a Pod consists of “one or more containers.” The conventional wisdom on containers-per-Pod is that a given Pod should include only one container if possible, and multiple containers only when they are tightly coupled. Sometimes, your Kubernetes configuration may necessitate a multi-container Pod—when using an Istio service mesh, for example, each Pod will include a “sidecar” container in addition to the primary container. But we wouldn’t expect to see more than two or three containers in most Pods. (We’ll talk more about service meshes later, but if you’re ready to dive into the deep end now, you can check out Service Mesh for Mere Mortals.)
Testing the limits
“Sure,” I hear you say, “I understand that we should run no more than a handful of containers per Pod. But I'm a maverick! I want to test the limits. How many containers can we run in a Pod?”
Short answer: There's no defined limit. As of version 1.23, the Kubernetes docs note that Kubernetes is designed for a maximum of 300,000 total containers. Technically, you could cram all of them into one Pod (assuming you could satisfy the underlying compute and storage requirements). But the docs also tell us that the system is designed for no more than 150,000 Pods, and that ratio is instructive. Operating at the extremities of scale, we would have two containers per Pod.
By separating out the functionality of your containerized services into minimal viable units, you can scale those units—those services or microservices—independently. Say, for example, that you have a Pod representing the core functionality of the auth service on your web app, and you suddenly experience a barrage of sign-ups. The cluster can scale up your auth service as needed without needlessly duplicating other, unrelated elements of the app.
Creating our first Pods
We can launch a Pod with a line in kubectl:
% kubectl run nginx --image=nginx
Here, we’re starting a new Pod named nginx based on the latest NGINX image in Docker Hub. We can see our Pod running with…
% kubectl get pods
Suppose we want to see what our Pod is serving. NGINX is running on port 80 within the Pod. We can forward that port to localhost:8000 on our local machine with…
% kubectl port-forward pod/nginx 8000:80
Press CTRL-C to stop the port-forwarding process.
This may remind you of working with Docker–we have a powerful command line tool that allows us to spin up containerized apps quickly. But as with Docker, we need a way to define, store, and re-use operations more complicated than launching a simple NGINX container.
In Kubernetes, we can define the attributes of an object we intend to include in our cluster with a manifest in YAML format. (YAML stands for Yet Another Markup Language or YAML Ain’t Markup Language, depending on who you ask.) YAML files use the .yml or .yaml file extensions, and a very simple one might look like this:
The YAML file is a list of key-value pairs. Often, values are nested objects indicated by hierarchical indentation. For example, the value for metadata above is a nested object containing two keys: name and labels.
Let’s walk through this manifest:
First, we establish the API version endpoint we’re using.
Next we note the kind of object we want to define: a Pod.
Then we establish some metadata: the name of this Pod object and labels that will help us refer to it later.
Finally, the spec establishes that we want a Pod running the NGINX web server.
These four top-level fields are required for every Kubernetes object manifest. When you submit the manifest to the API server, it will cue the scheduler to find an appropriate node and then instruct the kubelet on that node to spin up a Pod including an nginx container. The container runtime will use the image of the specified name found in the system’s configured image registry. (Your Minikube setup uses Docker Hub.)
Copy the YAML manifest above into a file named pod.yml
. Navigate to the directory where you’ve stored the file, and you can launch a Pod based on the manifest with…
% kubectl apply -f pod.yml
When you run kubectl get pods
again, you should see both of the Pods you’ve launched. Try port-forwarding the NGINX process from the nginx-2 Pod. Then delete the running Pods:
% kubectl delete pod nginx
% kubectl delete pod nginx-2
JSON is YAML
YAML is a superset of JavaScript Object Notation (or JSON), which means that valid JSON can be included in YAML manifests–and indeed, a file full of JSON with the .yml or .yaml extension can be a valid YAML manifest.
If you’re not familiar with JSON, don’t worry about it for now—but if you are, it can provide some useful insight into what’s happening in your YAML files. For example, the first line of the YAML manifest we just saw could be written:
{“apiVersion”: “v1”}
So why use YAML rather than JSON, especially given that the Kubernetes API server “speaks” JSON? Some people find YAML more readable, but the better answer is that YAML supports comments, which are as important in our manifests as in our code.
In review…
We’ve covered a lot of ground in this lesson:
The nature and role of Kubernetes objects
The Pod object
YAML manifests
Finally, we got some practice working with individual Pods. But if we want to efficiently create a service at scale, we don’t want to do it atom by atom. In practice, we don’t want to interact with individual Pods very often, since they’re ephemeral by design. Instead, we need an abstraction to define our intent at a higher level.
Fortunately, Kubernetes gives us an object to do just that: the Deployment. In the next lesson, we’ll discuss this essential tool for creating and managing groups of Pods.