Kubernetes in Action Second Edition p01 50
Kubernetes in Action Second Edition p01 50
1. Copyright_2023_Manning_Publications
2. welcome
3. 1_Introducing_Kubernetes
4. 2_Understanding_containers
5. 3_Deploying_your_first_application
6. 4_Introducing_Kubernetes_API_objects
7. 5_Running_workloads_in_Pods
8. 6_Manging_the_Pod_lifecycle
9. 7_Attaching_storage_volumes_to_Pods
10. 8_Persisting_data_in_PersistentVolumes
11. 9_Configuration_via_ConfigMaps,_Secrets,_and_the_Downward_API
12. 10_Organizing_objects_using_Namespaces_and_Labels
13. 11_Exposing_Pods_with_Services
14. 12_Exposing_Services_with_Ingress
15. 13_Replicating_Pods_with_ReplicaSets
16. 14_Managing_Pods_with_Deployments
17. 15_Deploying_stateful_workloads_with_StatefulSets
18. 16_Deploying_node_agents_and_daemons_with_DaemonSets
19. 17_Running_finite_workloads_with_Jobs_and_CronJobs
MEAP Edition
Kubernetes in Action
Second edition
Version 15
https://livebook.manning.com/book/kubernetes-in-action-
secondedition/discussion
manning.com
welcome
Thank you for purchasing the MEAP for Kubernetes in Action, Second
Edition.
In 2015 I was asked by Manning to write the first edition of this book. The
originally planned 300-page book grew to over 600 pages full of
information. The writing forced me to also research those parts of
Kubernetes that I wouldn’t have looked at more closely otherwise. I put
most of what I learned into the book. Judging by their reviews and
comments, readers love a detailed book like this.
The plan for the second edition of the book is to add even more information
and to rearrange some of the existing content. The exercises in this book
will take you from deploying a trivial application that initially uses only the
basic features of Kubernetes to a full-fledged application that incorporates
additional features as the book introduces them.
The book is divided into five parts. In the first part, after the introduction
of Kubernetes and containers, you’ll deploy the application in the simplest
way. In the second part you’ll learn the main concepts used to describe and
deploy your application. After that you’ll explore the inner workings of
Kubernetes components. This will give you a good foundation to learn the
difficult part - how to manage Kubernetes in production. In the last part of
the book you’ll learn about best practices and how to extend Kubernetes.
I hope you all like this second edition even better than the first, and if you’re
reading the book for the first time, your feedback will be even more
valuable. If any part of the book is difficult to understand, please post your
questions, comments or suggestions in the liveBook forum.
Thank you for helping me write the best book possible.
—Marko Lukša
In this book
Before you can learn about the ins and outs of running applications with
Kubernetes, you must first gain a basic understanding of the problems
Kubernetes is designed to solve, how it came about, and its impact on
application development and deployment. This first chapter is intended to
give a general overview of these topics.
After learning more about what Kubernetes does, you’ll find that the name
hits the spot perfectly. A helmsman maintains the course of the ship,
carries out the orders given by the captain and reports back the ship's
heading. Kubernetes steers your applications and reports on their status
while you - the captain - decide where you want the system to go.
How to pronounce Kubernetes and what is k8s?
Whenever you change the description, Kubernetes will take the necessary
steps to reconfigure the running application to match the new description,
as shown in the next figure.
Figure 1.3 Changes in the description are reflected in the running application
Taking on the daily management of applications
The sheer scale of Google’s workload has forced them to develop solutions
to make the development and management of thousands of software
components manageable and cost-effective. Over the years, Google
developed an internal system called Borg (and later a new system called
Omega) that helped both application developers and operators manage
these thousands of applications and services.
In addition to simplifying development and management, these systems
have also helped them to achieve better utilization of their infrastructure.
This is important in any organization, but when you operate hundreds of
thousands of machines, even tiny improvements in utilization mean savings
in the millions, so the incentives for developing such a system are clear.
Note
Data on Google’s energy use suggests that they run around 900,000
servers.
Over time, your infrastructure grows and evolves. Every new data center is
state-of-the-art. Its infrastructure differs from those built in the past.
Despite the differences, the deployment of applications in one data center
should not differ from deployment in another data center. This is especially
important when you deploy your application across multiple zones or
regions to reduce the likelihood that a regional failure will cause application
downtime. To do this effectively, it’s worth having a consistent method for
deploying your applications.
Based on the experience they gained while developing Borg, Omega and
other internal systems, in 2014 Google introduced Kubernetes, an
opensource project that can now be used and further improved by
everyone.
Figure 1.5 The origins and state of the Kubernetes open-source project
As soon as Kubernetes was announced, long before version 1.0 was
officially released, other companies, such as Red Hat, who has always been
at the forefront of open-source software, quickly stepped on board and
helped develop the project. It eventually grew far beyond the expectations
of its founders, and today is arguably one of the world’s leading open-
source projects, with dozens of organizations and thousands of individuals
contributing to it.
In the past, most applications were large monoliths. The components of the
application were tightly coupled, and they all ran in a single computer
process. The application was developed as a unit by a large team of
developers and the deployment of the application was straightforward. You
installed it on a powerful computer and provided the little configuration it
required. Scaling the application horizontally was rarely possible, so
whenever you needed to increase the capacity of the application, you had
to upgrade the hardware - in other words, scale the application vertically.
Then came the microservices paradigm. The monoliths were divided into
dozens, sometimes hundreds, of separate processes, as shown in the
following figure. This allowed organizations to divide their development
departments into smaller teams where each team developed only a part of
the entire system - just some of the microservices.
Figure 1.6 Comparing monolithic applications with microservices
Each microservice is now a separate application with its own development
and release cycle. The dependencies of different microservices will
inevitably diverge over time. One microservice requires one version of a
library, while another microservice requires another, possibly incompatible,
version of the same library. Running the two applications in the same
operating system becomes difficult.
Individual parts of the entire application no longer need to run on the same
computer, which makes it easier to scale the entire system, but also means
that the applications need to be configured to communicate with each
other. For systems with only a handful of components, this can usually be
done manually, but it’s now common to see deployments with well over a
hundred microservices.
With the advent of the Dev-ops paradigm, the two teams now work much
more closely together throughout the entire life of the software product.
The development team is now much more involved in the daily
management of the deployed software. But that means that they now need
to know about the infrastructure on which it’s running.
As a software developer, your primary focus is on implementing the
business logic. You don’t want to deal with the details of the underlying
servers. Fortunately, Kubernetes hides these details.
Over the past decade or two, many organizations have moved their
software from local servers to the cloud. The benefits of this seem to have
outweighed the fear of being locked-in to a particular cloud provider, which
is caused by relying on the provider’s proprietary APIs to deploy and
manage applications.
Any company that wants to be able to move its applications from one
provider to another will have to make additional, initially unnecessary
efforts to abstract the infrastructure and APIs of the underlying cloud
provider from the applications. This requires resources that could
otherwise be focused on building the primary business logic.
One can imagine Kubernetes as an operating system for the cluster. The
next figure illustrates the analogies between an operating system running
on a computer and Kubernetes running on a cluster of computers.
Figure 1.8 Kubernetes is to a computer cluster what an Operating System is to a computer
The Workload Plane is sometimes referred to as the Data Plane, but this
term could be confusing because the plane doesn’t host data but
applications. Don’t be confused by the term “plane” either - in this context
you can think of it as the “surface” the applications run on.
Non-production clusters can use a single master node, but highly available
clusters use at least three physical master nodes to host the Control Plane.
The number of worker nodes depends on the number of applications you’ll
deploy.
When I say that all worker nodes become one space, I don’t want you to
think that you can deploy an extremely large application that is spread
across several small machines. Kubernetes doesn’t do magic tricks like this.
Each application must be small enough to fit on one of the worker nodes.
If you don’t care which node your application lands on, it also means that
it can be moved to any other node at any time without you having to worry
about it. Kubernetes may need to do this to make room for a larger
application that someone wants to deploy. This ability to move applications
allows the applications to be packed tightly together so that the resources
of the nodes can be utilized in the best possible way.
Note
When you run Kubernetes on cloud infrastructure, it can even increase the
size of your cluster by provisioning additional nodes through the cloud
provider’s API. This way, you never run out of space to run additional
instances of your applications.
Kubernetes also makes every effort to ensure that your applications run
smoothly. If your application crashes, Kubernetes will restart it
automatically. So even if you have a broken application that runs out of
memory after running for more than a few hours, Kubernetes will ensure
that your application continues to provide the service to its users by
automatically restarting it in this case.
A set of master nodes that host the Control Plane components, which
are the brains of the system, since they control the entire cluster. A
set of worker nodes that form the Workload Plane, which is where your
workloads (or applications) run.
The following figure shows the two planes and the different nodes they
consist of.
Figure 1.11 The two planes that make up a Kubernetes cluster
The two planes, and hence the two types of nodes, run different
Kubernetes components. The next two sections of the book introduce
them and summarize their functions without going into details. These
components will be mentioned several times in the next part of the book
where I explain the fundamental concepts of Kubernetes. An in-depth look
at the components and their internals follows in the third part of the book.
The components of the Control Plane hold and control the state of the
cluster, but they don’t run your applications. This is done by the (worker)
nodes.
The Kubelet, an agent that talks to the API server and manages the
applications running on its node. It reports the status of these
applications and the node via the API.
The Container Runtime, which can be Docker or any other runtime
compatible with Kubernetes. It runs your applications in containers as
instructed by the Kubelet.
The Kubernetes Service Proxy (Kube Proxy) load-balances network
traffic between applications. Its name suggests that traffic flows
through it, but that’s no longer the case. You’ll learn why in chapter
14.
Add-on components
Most Kubernetes clusters also contain several other components. This
includes a DNS server, network plugins, logging agents and many others.
They typically run on the worker nodes but can also be configured to run
on the master.
For now, I only expect you to be vaguely familiar with the names of these
components and their function, as I’ll mention them many times
throughout the following chapters. You’ll learn snippets about them in
these chapters, but I’ll explain them in more detail in chapter 14.
I’m not a fan of explaining how things work until I first explain what
something does and teach you how to use it. It’s like learning to drive. You
don’t want to know what’s under the hood. At first, you just want to learn
how to get from point A to B. Only then will you be interested in how the
car makes this possible. Knowing what’s under the hood may one day help
you get your car moving again after it has broken down and you are
stranded on the side of the road. I hate to say it, but you’ll have many
moments like this when dealing with Kubernetes due to its sheer
complexity.
YAML was initially said to mean “Yet Another Markup Language”, but it was
latter changed to the recursive acronym “YAML Ain’t Markup Language”.
It’s one of the ways to serialize an object into a humanreadable text file.
Definition
1. You submit the application manifest to the Kubernetes API. The API
Server writes the objects defined in the manifest to etcd.
2. A controller notices the newly created objects and creates several new
objects - one for each application instance.
3. The Scheduler assigns a node to each instance.
4. The Kubelet notices that an instance is assigned to the Kubelet’s node.
It runs the application instance via the Container Runtime.
5. The Kube Proxy notices that the application instances are ready to
accept connections from clients and configures a load balancer for
them.
6. The Kubelets and the Controllers monitor the system and keep the
applications running.
The procedure is explained in more detail in the following sections, but the
complete explanation is given in chapter 14, after you have familiarized
yourself with all the objects and controllers involved.
After you’ve created your YAML or JSON file(s), you submit the file to the
API, usually via the Kubernetes command-line tool called kubectl.
Note
Kubectl splits the file into individual objects and creates each of them by
sending an HTTP PUT or POST request to the API, as is usually the case with
RESTful APIs. The API Server validates the objects and stores them in the
etcd datastore. In addition, it notifies all interested components that these
objects have been created. Controllers, which are explained next, are one
of these components.
The Kubelet that runs on each worker node is also a type of controller. Its
task is to wait for application instances to be assigned to the node on which
it is located and run the application. This is done by instructing the
Container Runtime to start the application’s container.
Once the application is up and running, the Kubelet keeps the application
healthy by restarting it when it terminates. It also reports the status of the
application by updating the object that represents the application instance.
The other controllers monitor these objects and ensure that applications
are moved to healthy nodes if their nodes fail.
When the number of workloads decreases and some worker nodes are left
without running workloads, Kubernetes can ask the cloud provider to
destroy the virtual machines of these nodes to reduce your operational
costs. This elasticity of the cluster is certainly one of the main benefits of
running Kubernetes in the cloud.
The first half of this book focuses on just using Kubernetes. You’ll run the
exercises in a local development cluster and on a managed GKE cluster, as
I find it’s the easiest to use and offers the best user experience. The second
part of the book gives you a solid foundation for managing Kubernetes, but
to truly master it, you’ll need to gain additional experience.
The first thing you need to be honest about is whether you need to
automate the management of your applications at all. If your application is
a large monolith, you definitely don’t need Kubernetes.
Even if you deploy microservices, using Kubernetes may not be the best
option, especially if the number of your microservices is very small. It’s
difficult to provide an exact number when the scales tip over, since other
factors also influence the decision. But if your system consists of less than
five microservices, throwing Kubernetes into the mix is probably not a good
idea. If your system has more than twenty microservices, you will most
likely benefit from the integration of Kubernetes. If the number of your
microservices falls somewhere in between, other factors, such as the ones
described next, should be considered.
Can you afford to invest your engineers’ time into learning Kubernetes?
Kubernetes is designed to allow applications to run without them knowing
that they are running in Kubernetes. While the applications themselves
don’t need to be modified to run in Kubernetes, development engineers
will inevitably spend a lot of time learning how to use Kubernetes, even
though the operators are the only ones that actually need that knowledge.
It would be hard to tell your teams that you’re switching to Kubernetes and
expect only the operations team to start exploring it. Developers like shiny
new things. At the time of writing, Kubernetes is still a very shiny thing.
Although Kubernetes has been around for several years at the time of
writing this book, I can’t say that the hype phase is over. The initial
excitement has just begun to calm down, but many engineers may still be
unable to make rational decisions about whether the integration of
Kubernetes is as necessary as it seems.
1.4 Summary
In this introductory chapter, you’ve learned that:
So far, you’ve only observed the ship from the pier. It’s time to come
aboard. But before you leave the docks, you should inspect the shipping
containers it’s carrying. You’ll do this next.
2 Understanding containers
This chapter covers
Understanding what a container is
Differences between containers and virtual machines
Creating, running, and sharing a container image with Docker
Linux kernel features that make containers possible
Unlike VMs, which each run a separate operating system with several
system processes, a process running in a container runs within the existing
host operating system. Because there is only one operating system, no
duplicate system processes exist. Although all the application processes run
in the same operating system, their environments are isolated, though not
as well as when you run them in separate VMs. To the process in the
container, this isolation makes it look like no other processes exist on the
computer. You’ll learn how this is possible in the next few sections, but first
let’s dive deeper into the differences between containers and virtual
machines.
Compared to VMs, containers are much lighter, because they don’t require
a separate resource pool or any additional OS-level processes. While each
VM usually runs its own set of system processes, which requires additional
computing resources in addition to those consumed by the user
application’s own process, a container is nothing more than an isolated
process running in the existing host OS that consumes only the resources
the app consumes. They have virtually no overhead.
Figure 2.1 shows two bare metal computers, one running two virtual
machines, and the other running containers instead. The latter has space
for additional containers, as it runs only one operating system, while the
first runs three – one host and two guest OSes.
Figure 2.1 Using VMs to isolate groups of applications vs. isolating individual apps with containers
Because of the resource overhead of VMs, you often group multiple
applications into each VM. You may not be able to afford dedicating a
whole VM to each app. But containers introduce no overhead, which
means you can afford to create a separate container for each application.
In fact, you should never run multiple applications in the same container,
as this makes managing the processes in the container much more difficult.
Moreover, all existing software dealing with containers, including
Kubernetes itself, is designed under the premise that there’s only one
application in a container. But as you’ll learn in the next chapter,
Kubernetes provides a way to run related applications together, yet still
keep them in separate containers.
Note
Two types of hypervisors exist. Type 1 hypervisors don’t require running a
host OS, while type 2 hypervisors do.
Containers, on the other hand, all make system calls on the single kernel
running in the host OS. This single kernel is the only one that executes
instructions on the host’s CPU. The CPU doesn’t need to handle any kind of
virtualization the way it does with VMs.
Examine the following figure to see the difference between running three
applications on bare metal, running them in two separate virtual machines,
or running them in three containers.
Figure 2.3 The difference between running applications on bare metal, in virtual machines, and in
containers
In the first case, all three applications use the same kernel and aren’t
isolated at all. In the second case, applications A and B run in the same VM
and thus share the kernel, while application C is completely isolated from
the other two, since it uses its own kernel. It only shares the hardware with
the first two.
The third case shows the same three applications running in containers.
Although they all use the same kernel, they are isolated from each other
and completely unaware of the others’ existence. The isolation is provided
by the kernel itself. Each application sees only a part of the physical
hardware and sees itself as the only process running in the OS, although
they all run in the same OS.
Figure 2.4 shows three main Docker concepts that appear in the process
I’ve just described. Here’s what each of them is:
As the next figure shows, another person can now pull the image to any
other computer running Docker and run it. Docker creates an isolated
container based on the image and invokes the executable file specified in
the image.
Figure 2.7 Running a container on a different computer
Running the application on any computer is made possible by the fact that
the environment of the application is decoupled from the environment of
the host.
For example, if you package your application with the files of the entire Red
Hat Enterprise Linux (RHEL) operating system and then run it, the
application will think it’s running inside RHEL, whether you run it on your
Fedora-based or a Debian-based computer. The Linux distribution installed
on the host is irrelevant. The only thing that might be important is the
kernel version and the kernel modules it loads. Later, I’ll explain why.
Unlike virtual machine images, which are big blobs of the entire filesystem
required by the operating system installed in the VM, container images
consist of layers that are usually much smaller. These layers can be shared
and reused across multiple images. This means that only certain layers of
an image need to be downloaded if the rest were already downloaded to
the host as part of another image containing the same layers.
Layers make image distribution very efficient but also help to reduce the
storage footprint of images. Docker stores each layer only once. As you can
see in the following figure, two containers created from two images that
contain the same layers use the same files.
Figure 2.8 Containers can share image layers
The figure shows that containers A and B share an image layer, which
means that applications A and B read some of the same files. In addition,
they also share the underlying layer with container C. But if all three
containers have access to the same files, how can they be completely
isolated from each other? Are changes that application A makes to a file
stored in the shared layer not visible to application B? They aren’t. Here’s
why.
When you delete a file, it is only marked as deleted in the read/write layer,
but it’s still present in one or more of the layers below. What follows is that
deleting files never reduces the size of the image.
WARNING
Even seemingly harmless operations such as changing permissions or
ownership of a file result in a new copy of the entire file being created in
the read/write layer. If you perform this type of operation on a large file or
many files, the image size may swell significantly.
And it’s not just about the kernel and its modules. It should also be clear
that a containerized app built for a specific hardware architecture can only
run on computers with the same architecture. You can’t put an application
compiled for the x86 CPU architecture into a container and expect to run it
on an ARM-based computer just because Docker is available there. For this
you would need a VM to emulate the x86 architecture.
Installing Docker
Ideally, you’ll install Docker directly on a Linux computer, so you won’t have
to deal with the additional complexity of running containers inside a VM
running within your host OS. But, if you’re using macOS or Windows and
don’t know how to set up a Linux VM, the Docker Desktop application will
set it up for you. The Docker command-line (CLI) tool that you’ll use to run
containers will be installed in your host OS, but the Docker daemon will run
inside the VM, as will all the containers it creates.
The Docker Platform consists of many components, but you only need to
install Docker Engine to run containers. If you use macOS or Windows,
install Docker Desktop. For details, follow the instructions at
http://docs.docker.com/install.
Note
Docker Desktop for Windows can run either Windows or Linux containers.
Make sure that you configure it to use Linux containers, as all the examples
in this book assume that’s the case.
After the installation is complete, you use the docker CLI tool to run
Docker commands. Let’s try pulling and running an existing image from
Docker Hub, the public image registry that contains ready-to-use container
images for many well-known software packages. One of them is the busybox
image, which you’ll use to run a simple echo "Hello world" command in your
first container.