Kubernetes Architecture

Kubernetes Architecture

The assumption in this set of notes is that the reader is already familiar with basic concepts such as containers (e.g. docker). If you are not please go through section 1.2 & 2.1 in the 1st edition or chapter 2 of the 2nd edition of the LUKSA book.

What it means to be cloud native

Below is the most up to date definition according to the Cloud Native Computing Foundation (CNCF).

“Cloud-Native technologies enable organizations to create scalable, agile services and applications in dynamic, distributed environments. Tools and standards achieve these goals through enabling effective automation with useful abstractions and declarative APIs. Systems are designed to be resilient, elastic, and loosely coupled and allow engineers to observe and safely make impactful changes. They are portable to public, private and hybrid clouds by adopting composable, vendor-neutral interfaces.

The Cloud Native Computing Foundation seeks to drive adoption of these techniques by fostering an ecosystem of open-source projects that align with these objectives. We democratize the state-of-the-art patterns and practices to ensure that innovations remain secure and operable for everyone.”

Apart from the definition above there is a shorter definition that I use to describe cloud native architectures: An architecture is cloud native when everything is replaceable or extensible without affecting the rest of the system.

A prime example of a cloud native architecture is Kubernetes (K8s) as described next.

Kubernetes Fundamentals

what-is-k8s What is K8s

Kubernetes can be thought of as an operating system for the cluster. It relieves application developers from having to implement certain infrastructure related services into their apps; instead they rely on Kubernetes to provide these services. This includes things such as

  • service discovery,
  • scaling,
  • load-balancing,
  • self-healing,
  • leader election.

Application developers can therefore focus on implementing the actual features of the applications and not waste time figuring out how to integrate them with the infrastructure.

High Level Kubernetes Architecture

The system is composed of a master node and any number of worker nodes. When the developer submits a list of apps to the master, Kubernetes deploys them to the cluster of worker nodes. What node a component lands on doesn’t (and shouldn’t) matter—neither to the developer nor to the system administrator.

cluster Master and app descriptors in the cluster

The developer can specify that certain apps must run together and Kubernetes will deploy them on the same worker node. Others will be spread around the cluster, but they can talk to each other in the same way, regardless of where they’re deployed.

More specifically:

  • The master node, which hosts the Kubernetes Control Plane, controls and manages the whole Kubernetes system
  • Worker nodes that run the actual applications you deploy (Data Plane)

master-worker-architecture Master-Worker Components

The C-plane components are:

  • The Kubernetes API Server, which you and the other Control Plane components communicate with,
  • The Scheduler, which schedules your apps (assigns a worker node to each deployable component of your application)
  • The Controller Manager, which performs cluster-level functions, such as replicating components, keeping track of worker nodes, handling node failures, and so on
  • The etcd, a reliable distributed data store that persistently stores the cluster configuration.

The worker node components are:

  • Docker / rkt, or another container runtime, which runs your containers
  • The kubelet, which talks to the API server and manages containers on its node
  • The Kubernetes Service Proxy (kube-proxy), which load-balances network traffic between application components

Beside the Control Plane components and the components running on the nodes, a few add-on components are required for the cluster to provide its services. This includes

  • The Kubernetes DNS server,
  • The Dashboard,
  • An Ingress controller,
  • Metrics Server,
  • The Container Network Interface (CNI) plugin

K8s Objects

Kubernetes objects are entities that are used to represent the state of the cluster. An object is a “record of intent” – once created, the cluster does its best to ensure it exists as defined. This is known as the cluster’s “desired state.” Kubernetes is always working to make an object’s “current state” equal to the object’s “desired state.” A desired state can describe for example:

  • What pods (containers) are running, and on which nodes
  • IP endpoints that map to a logical group of containers
  • How many replicas of a container are running

The above is obviously a non-exhaustive list. The objects that are worth detailing further are presented next.

Object Description
Pods Pods are the smallest deployable units of computing that you can create and manage in Kubernetes.
ReplicaSets A ReplicaSet’s purpose is to maintain a stable set of replica Pods running at any given time.
DeamonSet A DaemonSet ensures that all (or some) Nodes run a copy of a Pod.
Deployment A Deployment provides declarative updates for Pods ReplicaSets.
Job A Job creates one or more Pods and ensures that a specified number of them successfully terminate.
Service A Service is an abstraction which defines a logical set of Pods and a policy by which to access them (sometimes this pattern is called a micro-service). A pod may disappear at any time—because the node it’s running on has failed, because someone deleted the pod, or because the pod was evicted from an otherwise healthy node. When any of those occurs, a missing pod is replaced with a new one by the Replication-Controller, as described previously. This new pod gets a different IP address from the pod it’s replacing. This is where services come in—to solve the problem of ever-changing pod IP addresses, as well as exposing multiple pods at a single constant IP and port pair.

We interact with the k8s cluster via kubectl CLI tool

kubectl kubectl CLI

In the following we treat example deployment procedures that are best to be experienced in a local or cloud hosted K8s cluster.

Example K8s deployment Procedures

Pods Creation

pod-containers

A pod is a group of one or more tightly related containers that will always run together on the same worker node and in the same Linux namespace(s). Each pod is like a separate logical machine with its own IP, hostname, processes, and so on, running a single application. The application can be a single process, running in a single container, or it can be a main application process and additional supporting processes, each running in its own container.

Pods that run a single container: The “one-container-per-Pod” model is the most common Kubernetes use case; in this case, you can think of a Pod as a wrapper around a single container; Kubernetes manages Pods rather than managing the containers directly.

Pods that run multiple containers that need to work together: A Pod can encapsulate an application composed of multiple co-located containers that are tightly coupled and need to share resources. These co-located containers form a single cohesive unit of service—for example, one container serving data stored in a shared volume to the public, while a separate sidecar container refreshes or updates those files. The Pod wraps these containers, storage resources, and an ephemeral network identity together as a single unit.

A container that acts as a web server for files in a shared volume, and a separate “sidecar” container that updates those files from a remote source

All the containers in a pod will appear to be running on the same logical machine, whereas containers in other pods, even if they’re running on the same worker node, will appear to be running on a different one.

The figure below shows both steps you had to perform to get a container image running inside Kubernetes.

steps-run-app-in-k8s. Running the luksa/kubia container image in Kubernetes

When you ran the kubectl command, it created a new ReplicationController object in the cluster by sending a REST HTTP request to the Kubernetes API server. The ReplicationController then created a new pod, which was then scheduled to one of the worker nodes by the Scheduler.

Please note that ReplicationControllers have been superseded with ReplicaSets. They are equivalent in functionality they provide to you and the figures/text will be updated with the 2nd edition of the K8s book is out.

The Kubelet on that node saw that the pod was scheduled to it and instructed Docker to pull the specified image from the registry because the image wasn’t available locally. After downloading the image, Docker created and ran the container.

The ReplicationController makes sure there’s always exactly one instance of your pod running. Generally, ReplicationControllers are used to replicate pods (that is, create multiple copies of a pod) and keep them running.

Services and Load Balancing

To make the pod accessible from the outside, you’ll expose it through a Service object. You’ll create a special service of type LoadBalancer, because if you create a regular service (a ClusterIP service), like the pod, it would also only be accessible from inside the cluster. By creating a LoadBalancer-type service, an external load balancer will be created and you can connect to the pod through the load balancer’s public IP.

service

When a service is created, it gets a static IP, which never changes during the lifetime of the service. Instead of connecting to pods directly, clients should connect to the service through its constant IP address. The service makes sure one of the pods receives the connection, regardless of where the pod is currently running (and what its IP address is).

The process of creating the Service object, provisioning the load balancer and how it forwards connections into the cluster is shown in the next figure.

load-balancer Creating a load balanced service

While Kubernetes allows you to create so-called LoadBalancer services, it doesn’t provide the load balancer itself. If your cluster is deployed in the cloud, Kubernetes can ask the cloud infrastructure to provision a load balancer and configure it to forward traffic into your cluster. The infrastructure tells Kubernetes the IP address of the load balancer and this becomes the external address of your service.

Scaling

To scale up the number of replicas of your pod, you need to change the desired replica count on the ReplicationController. As one of the most fundamental Kubernetes principles, instead of telling Kubernetes exactly what actions it should perform, you’re only declaratively changing the desired state of the system and letting Kubernetes examine the current actual state and reconcile it with the desired state.

Now that we have multiple instances of your app running, guess what happens if you hit the service URL: Requests are hitting different pods randomly.

app-scaling

This is what services in Kubernetes do when more than one pod instance backs them. They act as a load balancer standing in front of multiple pods. When there’s only one pod, services provide a static address for the single pod. Whether a service is backed by a single pod or a group of pods, those pods come and go as they’re moved around the cluster, which means their IP addresses change, but the service is always there at the same address. This makes it easy for clients to connect to the pods, regardless of how many exist and how often they change location.

scaling

Job

Kubernetes job type resources supports batch processing that is usually last for many hours or days to complete.

job

The job resource type allows for running a pod whose container (run) isn’t restarted when the process running inside finishes successfully. Once it does, the pod is considered complete. In the event of a node failure, the pods on that node that are managed by a Job will be rescheduled to other nodes the way ReplicaSet pods are. In the event of a failure of the process itself (when the process returns an error exit code), the Job can be configured to either restart the container or not.A pod’s time can be limited by setting the activeDeadlineSeconds property in the pod spec. If the pod runs longer than that, the system will try to terminate it and will mark the Job as failed.In the case where jobs must be executed either periodically or once in the future, a CronJob resource can be created. The schedule for running the job is specified in the well-known cron format.

The following popular video deconstructs K8s in 30mins and is highly recommended:

Kubernetes Deconstructed: Understanding Kubernetes by Breaking It Down Kubernetes Deconstructed: Understanding Kubernetes by Breaking It Down - Carson Anderson, DOMO