DEV Community

Frank Snelling
Frank Snelling

Posted on AI-assisted

kubernetes for engineers who know literally nothing

Disclaimer: this is not a post from a seasoned DevOps engineer. So if you also know very little about Kubernetes, ignorance loves company.

To start with some (somewhat) fun facts:

  • Kubernetes was created by Google (in 2014)
  • Kubernetes is also known as K8s (K********s = K + 8 letters + s)

Why would you even need Kubernetes?

Well, lets start simple. Imagine you have created a report generation app. It has 2 services - a backend and a frontend. People upload some information and you return a .txt report.

But obviously returning a .txt document is ugly. Users want graphs, nice fonts, interactive links. You need to create PDFs. But creating PDFs can be heavy work and your backend needs to remain fast and responsive. Introducing PDF generation inside the backend will seriously slow stuff down.

So you decide to add a third service for PDF generation. This means your main backend service remains fast and responsive, while the heavy work is done separately.

A lot more moving parts right? And each part has different deployment requirements too. The PDF generation service needs a lot more memory than the backend to generate the PDFs. Plus, when there are a surge of PDF requests you need extra resources to deal with load. You probably want a queue too. And we haven't even added state yet to keep track of everything! So add a database, cron jobs for S3 lifecycle, observability, and a bunch of networking rules between all your services.

As the application grows, it's going to get harder and harder to manage everything. Because this is just one feature in one simple app. Imagine a company with hundreds of teams and hundreds of services.

Before getting into Kubernetes, let's start with a key concept. The container.

A container is an isolated process with its own packaged environment. In other words, a running app with the code you've written and the packages you've installed. Containers are the solution to "well it works for me!" because building a container involves declaring dependencies and versions. This makes it easy to run the app consistently across different environments.

With our report generation app, we would package the backend, the frontend, and the PDF generation service into separate containers. This means each service can run independently with as many instances as we need. For example, running several PDF generation containers during peak times.

Once you've defined containers, this is where Kubernetes comes in.

Kubernetes allows you to manage all of the containers in your system.

You define the desired state of your system and Kubernetes works to make this a reality.

So what does your "desired state" actually look like?

Well for us, our desired state might be 3 PDF generation containers running at any time. Or it could be more complex too, like having more instances when traffic is heavy and less instances when traffic is light, to optimise cost and latency. We can define these rules with Kubernetes.

With this basic mental model, we can start unpacking the key parts of Kubernetes.

At the top level is the cluster. A cluster is a group of machines — known as nodes — managed by Kubernetes.

The cluster acts as an operational boundary — for example, you might have a production and a staging cluster. Within the cluster, there are two key things:

  1. The control plane
  2. The worker nodes

The control plane manages the worker nodes. It stores your instructions and works to enforce them. The worker nodes actually run your containers.

So who manages the control plane? Well you can do this yourself, but often you will use a managed Kubernetes provider (for example, Google Kubernetes Engine - GKE) if you're on the cloud.

The control plane acts as the brains of your system and it has a few key components:

  • The API server is how you communicate with the control plane.
  • etcd is a key/value database that Kubernetes uses to store cluster configuration and state.
  • The scheduler decides which node to run stuff on.
  • The controller manager... manages your controllers (more on this).

The worker nodes are easy to understand as a hierarchy.

cluster
└── worker node
    └── pod
        └── container
Enter fullscreen mode Exit fullscreen mode

We are already familiar with the cluster. The worker node is simply a machine, like one you might rent from a cloud provider. A pod seems a little harder to define and is often described as:

"The smallest deployable units of computing that you can create and manage in Kubernetes" K8s docs.

A pod is defined by what it contains, which is a collection of containers. Usually a pod contains just a single application container. So our PDF generation container, frontend container, and backend container would all have separate pods. A pod usually only has multiple containers if you need a helper container, for example something for collecting logs.

Instead of scaling up and down raw containers, Kubernetes manages pods. This means multiple containers can "live together", sharing storage, an IP address, and lifecycle. But a pod is ephemeral. When it dies, it is recreated (not repaired) and it gets a new identity.

TLDR - a cluster is a group of nodes, a node contains a group of pods, and a pod contains a group of containers.

Kubernetes manages nodes and pods to enforce our desired state. But how do we define what is "desired"?

Resources. Everything in Kubernetes is a resource. You define the resources you desire in a .yaml manifest. A simplified manifest might look like this:

apiVersion: apps/v1
kind: Deployment

metadata:
  name: pdf-generator

spec:
  replicas: 3

  selector:
    matchLabels:
      app: pdf-generator

  template:
    metadata:
      labels:
        app: pdf-generator

    spec:
      containers:
        - name: pdf-generator
          image: my-company/pdf-generator:v1
          resources:
            requests:
              cpu: "500m"
              memory: "1Gi"
Enter fullscreen mode Exit fullscreen mode

The manifest has a few key fields:

  • Kubernetes updates its APIs, so apiVersion is which one this yaml is using.
  • kind is the type of resource.
  • metadata is information about the resource.
  • spec is where the desired state lives. Its content and meaning depends on the resource kind.

In our example above, we have defined kind: Deployment. This means the resource is a Deployment. There are many resource kinds defined by Kubernetes (and you can even create custom resources). Basically everything in Kubernetes is a resource, even things we don't define ourselves or manage directly. For example, pods and nodes are also resources.

A Deployment is a resource for running stateless applications with a certain number of replicas. A replica is an interchangeable copy of a pod. We want multiple replicas of our PDF generation service so we can handle many requests (and so we have backups in case one replica goes down).

 So Kubernetes knows what we desire, how does it enforce it?

Controllers. Controllers do the following loop called reconciliation:

observe -> compare -> act -> repeat
Enter fullscreen mode Exit fullscreen mode

In our manifest above, we requested 3 replicas. The deployment controller creates and updates a ReplicaSet. A ReplicaSet controller ensures the requested replicas exist. If one pod goes down:

  1. the ReplicaSet controller observes the current number of replicas.
  2. the ReplicaSet controller compares this with the requested number of replicas.
  3. the ReplicaSet controller acts by replacing it.

If we want to implement scaling depending on traffic, the deployment controller can update the number of requested replicas.

So you know a bit about Kubernetes now. But do you need it?

Maybe. But maybe not.

Kubernetes is undoubtedly powerful, but this comes with significant complexity. Kubernetes is widely used and growing (82% of container users run Kubernetes in production according to CNCF) but it's worth noting the other options. Cloud providers offer managed container platforms like Cloud Run and ECS/Fargate. You hand over your containers and the cloud provider manages the infrastructure.

You might think: "I'm not paying a premium for something like Cloud Run". But I wouldn't dismiss it too fast. While Kubernetes exposes all the primitives you need to optimise for cost, it is significant setup and ongoing work. And time is money. Not to mention that a poorly setup Kubernetes cluster can definitely cost more money then it should.

As always, it's all about tradeoffs.

Appendix

More Resource Kinds

  • A Pod runs 1+ containers.
  • A Deployment keeps a replicated app running.
  • A ReplicaSet maintains a desired number of pods.
  • A Service provides a stable identity to pods (because they get a new IP address when replaced).
  • An Ingress routes external traffic into the cluster.
  • A Job runs a task until it completes successfully.
  • A CronJob is a Job on a schedule.
  • A ConfigMap stores non-sensitive information.
  • A Secret stores sensitive information.
  • A Persistent Volume (PV) represents disk storage independent of a Pod. It survives even when a Pod dies.
  • A Persistent Volume Claim (PVC) is how you order a PV. A Pod mounts a PVC which is bound to a PV which represents an actual cloud resource.
  • A namespace creates a logical boundary in a cluster. This means you can have a backend Deployment in multiple namespaces (for example, production and staging). You can also apply policies to an entire namespace (for example, staging can use x much CPU and memory).

Top comments (0)