Introducing Pods (and other Kubernetes objects)

Eric Gregory - June 16, 2022

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

  1. What is Kubernetes?

  2. Setting Up a Kubernetes Learning Environment

  3. The Architecture of a Kubernetes Cluster

  4. Introducing Pods (and other Kubernetes objects) ← You are here


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
customresourcedefinitions         crd,crds
controllerrevisions                            apps/v1
daemonsets                        ds           apps/v1
deployments                       deploy       apps/v1
replicasets                       rs           apps/v1
statefulsets                      sts          apps/v1
horizontalpodautoscalers          hpa          autoscaling/v2
cronjobs                          cj           batch/v1
jobs                                           batch/v1
certificatesigningrequests        csr
events                            ev 
ingresses                         ing
networkpolicies                   netpol
poddisruptionbudgets              pdb          policy/v1
podsecuritypolicies               psp          policy/v1beta1
priorityclasses                   pc 
storageclasses                    sc 

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
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

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.