Kubernetes in The Enterprise Ebook
Kubernetes in The Enterprise Ebook
m
pl
im
Kubernetes in
en
ts
of
the Enterprise
Deploying and Operating Production
Applications on Kubernetes in
Hybrid Cloud Environments
ibm.biz/oreillykubernetes
Smart
Kubernetes in the
Enterprise
Deploying and Operating Production
Applications on Kubernetes in
Hybrid Cloud Environments
The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. Kubernetes in the
Enterprise, the cover image, and related trade dress are trademarks of O’Reilly
Media, Inc.
The views expressed in this work are those of the authors, and do not represent the
publisher’s views. While the publisher and the authors have used good faith efforts
to ensure that the information and instructions contained in this work are accurate,
the publisher and the authors disclaim all responsibility for errors or omissions,
including without limitation responsibility for damages resulting from the use of or
reliance on this work. Use of the information and instructions contained in this
work is at your own risk. If any code samples or other technology this work contains
or describes is subject to open source licenses or the intellectual property rights of
others, it is your responsibility to ensure that your use thereof complies with such
licenses and/or rights.
978-1-492-04324-9
[LSI]
To Wendy, for your love and encouragement. You will forever be
“unforgettable in every way” to me. To Samantha, for your fearlessness
and curiosity about all things in life. To David, for your inspirational
smile and laughter. To my mother, Betty, for your amazing tenacity
through all of life’s challenges while remaining optimistic about the
future.
—Michael Elder
Great thanks go to my wife, Becky, for her love and support. To Oren
goes my gratitude for his laughter and caring spirit. Thank you to my
parents Nancy and Barry Kitchener: without their example I would
not have the tenacity to take on the trials of life.
—Jake Kitchener
I dedicate this book to my wife, Janet; my daughter, Morgan; my son,
Ryan; and my parents, Harold and Mady Topol. I could not have done
this without your love and support during this process.
—Brad Topol
Table of Contents
Foreword. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ix
Preface. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi
v
Helm 49
Next Steps 51
5. Continuous Delivery. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Image Build 92
Programmability of Kubernetes 94
General Flow of Changes 94
vi | Table of Contents
8. Contributor Experience. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
Kubernetes Website 137
The Cloud Native Computing Foundation Website 138
IBM Developer Website 139
Kubernetes Contributor Experience SIG 140
Kubernetes Documentation SIG 141
Kubernetes IBM Cloud SIG 142
ix
Not for the first time, IBM is at the forefront of this change, in
projects such as Istio, etcd, Service Catalog, Cloud Foundry, and of
course, Kubernetes. I’ve personally worked with the authors to
spearhead adoption of Kubernetes and the Cloud Native Computing
Foundation that is its home. You are in the hands of experts here—a
team who have been leaders in the open source community as well
as put in the hard yards with real world deployments at scale.
In this book you will find that knowledge presented as a set of pat‐
terns and practices. Every business can apply these patterns to create
a production-grade cloud-native platform with Kubernetes at the
core. Reader, the applications are up to you—an exciting world is
just around the corner.
— Alexis Richardson
CEO, Weaveworks
TOC Chair, Cloud Native
Computing Foundation
x | Foreword
Preface
xi
Chapter 1 provides an overview of both containers and Kubernetes.
It then discusses the Cloud Native Computing Foundation (CNCF)
and the ecosystem growth that has resulted from its open gover‐
nance model and conformance certification efforts. In Chapter 2, we
provide an overview of Kubernetes architecture, describe several
ways to run Kubernetes, and introduce many of its fundamental
constructs including Pods, ReplicaSets, and Deployments. Chapter 3
covers more advanced Kubernetes capabilities such as load balanc‐
ing, volume support, and configuration primitives such as Config‐
Maps and Secrets, StatefulSets, and DaemonSets. Chapter 4 provides
a description of our production application that serves as our enter‐
prise Kubernetes workload. In Chapter 5, we present an overview of
continuous delivery approaches that are popular for enterprise
applications. Chapter 6 focuses on the operation of enterprise appli‐
cations, examining issues such as log collection and analysis and
health management of your microservices. Chapter 7 provides in-
depth coverage of operating Kubernetes environments and
addresses topics such as access control, autoscaling, networking,
storage, and their implications on hybrid cloud environments. We
offer a discussion of the Kubernetes developer experience in Chap‐
ter 8. Finally, in Chapter 9, we conclude with a discussion of areas
for future growth in Kubernetes.
Acknowledgments
We would like to thank the entire Kubernetes community for its
passion, dedication, and tremendous commitment to the Kuber‐
netes project. Without the code developers, code reviewers, docu‐
mentation authors, and operators contributing to the project over
the years, Kubernetes would not have the rich feature set, strong
adoption, and large ecosystem it has today.
We would also like to thank our Kubernetes colleagues, Zach Cor‐
leissen, Steve Perry, Joe Heck, Andrew Chen, Jennifer Randeau, Wil‐
liam Dennis, Dan Kohn, Paris Pittman, Jorge Castro, Guang Ya Liu,
Sahdev Zala, Srinivas Brahmaroutu, Morgan Bauer, Doug Davis,
Michael Brown, Chris Luciano, Misty Linville, Zach Arnold, and
Jonathan Berkhahn for the wonderful collaboration over the years.
We also extend our thanks to John Alcorn and Ryan Claussen, the
original authors of the example Kubernetes application we use as an
exemplar in the book. Also, we would like to thank Irina Delidja‐
kova for her review and wisdom for all things Db2.
xii | Preface
A very special thanks to Angel Diaz, Todd Moore, Vince Brunssen,
Alex Tarpinian, Dave Lindquist, Willie Tejada, Bob Lord, Jake Mor‐
lock, Peter Wassel, Dan Berg, Jason McGee, Arvind Krishna, and
Steve Robinson for all of their support and encouragement during
this endeavor.
Preface | xiii
CHAPTER 1
An Introduction to Containers
and Kubernetes
1
lize the new cloud environments to self-provision the same applica‐
tion infrastructure in less than a day. Life was good.
Although the new VM-based cloud environments were a huge step
in the right direction, they did have some notable inefficiencies. For
example, VMs could take a long time to start, and taking a snapshot
of the VM could take a significant amount of time as well. In addi‐
tion, each VM typically required a large number of resources, and
this limited the ability to fully exploit the utilization of the physical
servers hosting the VMs.
At Pycon in March of 2013, Solomon Hykes presented an approach
for deploying web applications to a cloud that did not rely on VMs.
Instead, Solomon demonstrated how Linux containers could be
used to create a self-contained unit of deployable software. This new
unit of deployable software was aptly named a container. Instead of
providing isolation at a VM level, isolation for the container unit of
software was provided at the process level. The process running in
the container was given its own isolated file system and was alloca‐
ted network connectivity. Solomon announced that the software
they created to run applications in containers was called Docker, and
would be made available as an open source project.
For many cloud application developers that were accustomed to
deploying VM-based applications, their initial experience with
Docker containers was mind-blowing. When using VMs, deploying
an application by instantiating a VM could easily take several
minutes. In contrast, deploying a Docker container image took just
a few seconds. This dramatic improvement in performance was
because instantiating a Docker image is more akin to starting a new
process on a Linux machine. This is a fairly lightweight operation,
especially when compared to instantiating a whole new VM.
Container images also showed superior performance when a cloud
application developer wanted to make changes to a VM image and
snapshot a new version. This operation was typically a very time-
consuming process because it required the entire VM disk file to be
written out. With Docker containers, a multilayered filesystem is
used instead. If changes are made in this situation, they are captured
as changes to the filesystem and represented by a new filesystem
layer. Because of this, a Docker container image could snapshot a
new version by writing out only the changes to the filesystem as a
new filesystem layer. In many cases, the amount of changes to the
1 Brendan Burns et al., “Borg, Omega, and Kubernetes: Lessons Learned from Three
Container-Management Systems over a Decade”. ACM Queue 14 (2016): 70–93.
2 Brendan Burns et al., “Borg, Omega, and Kubernetes: Lessons Learned from Three
Container-Management Systems over a Decade”. ACM Queue 14 (2016): 70–93.
Summary
This chapter discussed a variety of factors that have contributed to
Kubernetes becoming the de facto standard for the orchestration
and management of cloud-native computing applications. Its declar‐
ative model, built-in support for autoscaling, improved networking
model, health-check support, and the backing of the CNCF have
resulted in a vibrant and growing ecosystem for Kubernetes with
adoption across cloud applications and high-performance comput‐
ing domains. In Chapter 2, we begin our deeper exploration into the
architecture and capabilities of Kubernetes.
Kubernetes Architecture
Kubernetes architecture at a high level is relatively straightforward.
It is composed of a master node and a set of worker nodes. The nodes
can be either physical servers or virtual machines (VMs). Users of
the Kubernetes environment interact with the master node using
either a command-line interface (kubectl), an application program‐
ming interface (API), or a graphical user interface (GUI). The mas‐
ter node is responsible for scheduling work across the worker nodes.
In Kubernetes, the unit of work that is scheduled is called a Pod, and
a Pod can hold one or more container. The primary components
that exist on the master node are the kube-apiserver, kube-scheduler,
etcd, and the kube-controller-manager:
kube-apiserver
The kube-apiserver makes available the Kubernetes API that is
used to operate the Kubernetes environment.
9
kube-scheduler
The kube-scheduler component is responsible for selecting the
nodes on which Pods should be created.
kube-controller-manager
Kubernetes provides several high-level abstractions for support‐
ing replicas of Pods, managing nodes, and so on. Each of these
is implemented with a controller component, which we describe
later in this chapter. The kube-controller-manager is responsible
for managing and running controller components.
etcd
The etcd component is a distributed key–value store and is the
primary communication substrate used by master and worker
nodes. This component stores and replicates the critical infor‐
mation state of your Kubernetes environment. Kubernetes out‐
standing performance and scalability characteristics are
dependent on etcd being a highly efficient communication
mechanism.
The worker nodes are responsible for running the Pods that are
scheduled on them. The primary Kubernetes components that exist
on worker nodes are the kubelet, kube-proxy, and the container run‐
time:
kubelet
The kubelet is responsible for making sure that the containers
in each Pod are created and stay up and running. The kubelet
will restart containers upon recognizing that they have termi‐
nated unexpectedly.
kube-proxy
One of Kubernetes key strengths is the networking support it
provides for containers. The kube-proxy component provides
networking support in the form of connection forwarding, load
balancing, and the mapping of a single IP address to a Pod.
Container runtime
The container runtime component is responsible for actually
running the containers that exist in each Pod. Kubernetes sup‐
ports several container runtime environment options including
Docker, rkt, and containerd.
Kubernetes Architecture | 11
In the next section, we expand our understanding of the Kubernetes
architecture by learning about several ways to run Kubernetes.
Minikube
Minikube is a tool that enables you to run a single-node Kubernetes
cluster within a VM locally on your laptop. Minikube is well suited
for trying many of the basic Kubernetes examples that are presented
in the next section of this chapter, and you can also use it as a devel‐
opment environment. In addition, Minikube supports a variety of
VMs and container runtimes.
What’s a Pod?
Because Kubernetes provides support for the management and
orchestration of containers, you would assume that the smallest
deployable unit supported by Kubernetes would be a container.
However, the designers of Kubernetes learned from experience1 that
it was more optimal to have the smallest deployable unit be some‐
thing that could hold multiple containers. In Kubernetes, this small‐
est deployable unit is called a Pod. A Pod can hold one or more
application containers. The application containers that are in the
same Pod have the following benefits:
1 Brendan Burns et al. (2016). “Borg, Omega, and Kubernetes: Lessons Learned from
Three Container-Management Systems over a Decade”. ACM Queue 14: 70–93.
In this example, we have added a label with app as the key and web
server as the value. Other Kubernetes constructs can then do
searches that match on this value to find this Pod. If there is a group
of Pods with this label, they can all be found. This simple and ele‐
gant approach of identifying Pods is used heavily by several higher-
level Kubernetes abstractions that are described later in this chapter.
Similarly, the previous example also demonstrates that we have
added an annotation. In this case, the annotation has kuber
ReplicaSets
Kubernetes provides a high-level abstraction called a ReplicaSet that
is used to manage a group of Pod replicas across a cluster. The key
advantage of a ReplicaSet is that you get to declare the number of
Pod replicas that you desire to run concurrently. Kubernetes will
monitor your Pods and will always strive to ensure that the number
of copies running is the number you selected. If some of your Pods
terminate unexpectedly, Kubernetes will instantiate new versions of
them to take their place. For cloud application operators accus‐
tomed to being contacted in the middle of the night to restart a
crashed application, having Kubernetes instead automatically handle
this situation on its own is a much better alternative.
To create a ReplicaSet, you provide a specification that is similar to
the Pod specification shown in “How Do I Describe What’s in My
Pod?” on page 15. The ReplicaSet adds new information to the spec‐
ification to declare the number of Pod replicas that should be run‐
ning and also provide matching information that identifies which
Pods the ReplicaSet is managing. Here is an example YAML specifi‐
cation for a ReplicaSet:
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: nginx
labels:
app: webserver
annotations:
kubernetes.io/change-cause: "Update nginx to 1.7.9"
spec:
replicas: 3
selector:
matchLabels:
app: webserver
template:
metadata:
If we run kubectl get pods quickly enough, we see that the pod we
deleted is being terminated. The ReplicaSet realizes that it lost one
of its Pods. Because its YAML specification declares that its desired
state is three Pod replicas, the ReplicaSet starts a new instance of the
nginx container. Here’s the output of this command:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-fvtzq 1/1 Running 0 1m
nginx-jfxdn 1/1 Running 0 1m
nginx-kfgxk 1/1 Running 0 5s
nginx-v7kqq 0/1 Terminating 0 1m
After a short amount of time, if you run kubectl get pods again,
you’ll notice just the two original Pod replicas and the newly created
substitute Pod replica are present:
Deployments
Deployments are a high-level Kubernetes abstraction that not only
allow you to control the number of Pod replicas that are instanti‐
ated, but also provide support for rolling out new versions of the
Pods. Deployments rely upon the previously described ReplicaSet
resource to manage Pod replicas and then add Pod version manage‐
ment support on top of this capability. Deployments also enable
newly rolled out versions of Pods to be rolled back to previous ver‐
sions if there is something wrong with the new version of the Pods.
Furthermore, Deployments support two options for upgrading
Pods, Recreate and RollingUpdate:
Recreate
The Recreate Pod upgrade option is very straightforward. In
this approach the Deployment resource modifies its associated
ReplicaSet to point to the new version of the Pod. It then pro‐
ceeds to terminate all of the Pods. The ReplicaSet then notices
that all of the Pods have been terminated and thus spawns new
Pods to ensure that the number of desired replicas are up and
29
mism. Kubernetes thus provides a load balancer in the form of a
Service Object. The Service Object has the following critical features
that are tailored to supporting Pod replicas:1
Virtual IP allocation and load balancing support
When a Service Object is created for a group of Pod replicas, a
virtual IP address is created that is used to load balance across
all the Pod replicas. The virtual IP address, also referred to as a
cluster IP address, is a stable value and suitable for use by
Domain Name System (DNS) services.
Port mapping support
Service Objects support the ability to map from a port on the
cluster IP address to a port being used by Pod replicas. For
example, the Service Object might want to expose the Pod’s
application as running on port 80 even though the Pod replicas
are listening on a more generic port.
Built-in readiness-check support
Kubernetes Service Objects provide built-in readiness-check
support. With this capability, the load-balancing capabilities
provided by the Service Object are smart enough to avoid rout‐
ing requests to Pod replicas that are not ready to receive
requests.
Creating a Service Object for a Deployment of Pod replicas is very
straightforward. You can accomplish this by using the kubectl
expose command. Assuming that you have provisioned the nginx
Deployment example from Chapter 2, you can expose it as a service
by doing the following:
$ kubectl expose deployment nginx --port=80 --target-port=8000
1 Kubernetes: Up and Running by Kelsey Hightower, Brendan Burns, and Joe Beda
(O’Reilly). Copyright 2017 Kelsey Hightower, Brendan Burns, and Joe Beda,
928-1-491-93567-5.
DaemonSets
One common specialized use case when running Kubernetes appli‐
cations is the need to have a single Pod replica running on each
Node. The most common scenario for this is when a single monitor‐
ing or logging container application should be run on each Node.
Having two of these containers running on the same Node would be
redundant, and all Nodes need to have the monitoring/logging con‐
tainer running on it. For this use case, Kubernetes provides a Dae‐
monSet resource that ensures a single copy of the Pod replica will
run on each Node. The following example illustrates how to run a
single Pod replica on each Node using a DaemonSet:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nginx
labels:
app: webserver
spec:
selector:
matchLabels:
app: webserver
template:
metadata:
labels:
app: webserver
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
As shown in the example, a DaemonSet YAML looks very similar to
a ReplicaSet. It uses a selector to provide the labels this DaemonSet
will use to identify its Pod replicas. In the example, the selector for
this DaemonSet states that it is managing Pod replicas that have a
label with app as the key and webserver as its associated value. The
DaemonSet also has a template section and a nested spec section
that serve the same purpose as they do in ReplicaSets.
DaemonSets | 31
The notable differences of a DaemonSet from a ReplicaSet are also
illustrated in the previous example. The kind field is set to Daemon
Set, and there is no need to declare the number of replicas desired.
The number of replicas will always be set to be a value that matches
the placing of one Pod replica on each Node.
Customizing DaemonSets
In some situations, a DaemonSet is desired, but with the complicat‐
ing factor that we don’t want a Pod replica running on all the Nodes.
For this scenario, you can customize a DaemonSet to identify the
Nodes deserving of running the Pod replica by using a nodeSelec
tor construct. In this approach, a nodeSelector is used to define a
label that the DaemonSet will look for on all the Nodes in the clus‐
ter. The Nodes that have this label will be the ones that will have a
DaemonSet-managed Pod replica instantiated on them. As an illus‐
tration of this capability, we first label a Node (node1 in this exam‐
ple) on which we want to run the Pod replica by using the kubectl
label nodes command:
$ kubectl label nodes node1 needsdaemon=true
With the needsdaemon label added to the Node, you can modify and
customize this DaemonSet to include the appropriate label and node
Selector:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nginx
labels:
app: webserver
spec:
selector:
matchLabels:
app: webserver
template:
metadata:
labels:
app: webserver
spec:
nodeSelector:
needsdaemon: "true"
containers:
- name: nginx
image: nginx:1.7.9
StatefulSets
Many of the high-level constructs Kubernetes provides, such as Rep‐
licaSets, provide support for managing a set of Pod replicas that are
identical and hence interchangeable. When integrating stateful,
replicated services into Kubernetes, such as persistent databases,
standard identical Pod replicas are not sufficient to meet the
requirements of this class of applications. Instead, these applications
need Pods that have a unique ID so that the correct storage volume
can always be reattached to the correct Pod. To support this class of
applications, Kubernetes provides the StatefulSet resource. Stateful‐
Sets have the following characteristics:2
Stable hostname with unique index
Each Pod associated with the StatefulSet is allocated a persistent
hostname with a unique, monotonically increasing index
appended to the hostname.
Orderly deployment of Pod replicas
Each Pod associated with the StatefulSet is created sequentially
in order from lowest index to highest index.
Orderly deletion of Pod replicas
Each Pod associated with the StatefulSet is deleted sequentially
in order from highest index to lowest index.
2 Hightower, Kelsey, Brendan Burns, and Joe Beda. Kubernetes: Up and Running. Sebasto‐
pol: O’Reilly Media, 2017.
StatefulSets | 33
Orderly deployment and scaling of Pod replicas
Each Pod associated with the StatefulSet is scaled sequentially in
order from lowest index to highest index, and before a scaling
operation can be applied to a Pod all its predecessors must be
running.
Orderly automated rolling upgrades of Pod replicas
Each Pod associated with the StatefulSet is updated by deleting
and recreating each Pod sequentially in order from highest
index to lowest index.
Headless service associated with the StatefulSet
A Service Object that has no cluster virtual IP address assigned
to it is associated with the StatefulSet to manage the DNS entries
for each Pod. This Service does not load balance across the Pods
in the StatefulSet, because each Pod is unique and client
requests need to be always directed to the same Pod.
The YAML descriptions for StatefulSets look very similar to those
used for ReplicaSets. The following is an example StatefulSet that
also includes a YAML description for its headless service:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
labels:
app: webserver
spec:
serviceName: "nginx"
replicas: 3
selector:
matchLabels:
app: webserver
• The kind field is set to Service, denoting that the top portion of
the specification is for a Service resource.
• Note that this service is declared to be headless by setting the
clusterIP field to None.
• In the next section of the specification, the StatefulSet is
defined. In this portion, the kind field is set to StatefulSet. It
is also worth noting is that StatefulSet must identify the Service
that manages it.
• You accomplish this by setting the serviceName field to the
name declared in the Service.
• In this example, the name of the Service is nginx, and that is the
value placed in the serviceName field.
The remaining portions of the StatefulSet are the same types of val‐
ues that are set in ReplicaSets. A key portion of the StatefulSet that is
not shown in the preceding example is the creation of persistent vol‐
ume storage for each Pod in the StatefulSet. Kubernetes provides a
volumeClaimTemplate to manage the mapping of a volume with
each Pod, and the volume is automatically mounted when the Pod is
rescheduled. More detail on Kubernetes volume support is provided
in “Volumes and Persistent Volumes” on page 36.
StatefulSets | 35
To run the previous example, save it in a file named statefulset.yaml.
You can now run the example by doing the following:
$ kubectl apply -f statefulset.yaml
To see the Pods that are created with the index added to the names,
run the following command:
$ kubectl get pods
You will then see output that looks like the following:
NAME READY STATUS RESTARTS AGE
nginx-0 1/1 Running 0 16s
nginx-1 1/1 Running 0 3s
nginx-2 1/1 Running 0 2s
In the next section, we switch gears a little and focus on how Kuber‐
netes enables containers within a Pod to share filesystem directories
using the concept of Volumes.
Persistent Volumes
Most Twelve-Factor Apps discourage the use of disk-based persis‐
tent storage because it’s difficult to maintain concurrency at scale
without specific guarantees for distributed read and write semantics.
Kubernetes extends beyond just 12-factor apps, as we have seen with
DaemonSets and StatefulSets, which do have a legitimate need for
persistent storage.
StatefulSets such as databases, caches, and database services, or Dae‐
monSets such as monitoring agents require their Pods to have access
to storage that is more permanent and survives across the lifetimes
of multiple Pods. For this purpose, Kubernetes provides Persistent‐
Volume storage support. There are many implementations of Persis‐
tentVolume support for Kubernetes, and they typically are built on
top of some form of remote network storage capability.
Support for PersistentVolumes in most Kubernetes clusters begins
with the cluster administrator creating PersistentVolumes and mak‐
ing these available in the cluster. The Pods in the cluster acquire
access to the PersistentVolume through a resource abstraction called
a PersistentVolumeClaim that is also represented as a YAML docu‐
ment:
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: my-pv-claim
labels:
app: nginx
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
ConfigMaps
In many cases, there needs to be a way to pass configuration infor‐
mation into your container-based applications. Kubernetes provides
ConfigMaps as its mechanism to pass information into the contain‐
ers it manages. ConfigMaps store configuration information as key–
value pairs. The following is a sample ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: custom-config
namespace: default
data:
customkey1: foo
customkey2: bar
ConfigMaps | 41
To run this example, save the file as configmapPod-volume.yaml.
Make sure that you have already created the ConfigMap from config‐
map.yaml as described at the beginning of this section. When you
complete these steps, you can now run the example by doing the fol‐
lowing:
$ kubectl apply -f configmapPod-volume.yaml
To view the output from the container, use the kubectl logs <pod
Name> command, as follows:
$ kubectl logs configmapexample-volume
You will then see the following output confirming that the keys were
created:
customkey1
customkey2
foo
$
To delete the Pod that you just created, run the following command:
$ kubectl delete pod configmapexample-volume
You can see that an env section has been added to the containers
section. The env section declares two keys: KEY1 and KEY2. Each of
these will be environment variables that are created. The KEY1 envi‐
ronment variable is assigned the value stored in customkey1 of the
custom-config ConfigMap using the valueFrom and configMap
KeyRef constructs. In a similar fashion, the KEY2 environment vari‐
able receives the value from customkey2 in the custom-config
ConfigMap. The environment variables KEY1 and KEY2 can be used
in the command that is run by the container image. To illustrate
this, the previous example prints the values for KEY1 and KEY2 when
the container runs the command line provided.
To run the example, save the file as configmapPod-env.yaml. Make
sure that you have already created the ConfigMap from config‐
map.yaml, as described at the beginning of this section. When
you’ve completed these steps, you can now run the example yourself
by doing the following:
$ kubectl apply -f configmapPod-env.yaml
To view the output from the container, use the kubectl logs <pod
Name> command, as follows:
$ kubectl logs configmapexample
You will then see the following output confirming that the keys were
created:
customkey1: foo customkey2: bar
$
To delete the Pod that you just created, run the following command:
$ kubectl delete pod configmapexample
As you have seen in this example, ConfigMaps are extremely valua‐
ble for injecting configuration information into containers. But what
if you need to inject some form of sensitive information into a con‐
tainer, such as a user ID and password? A ConfigMap would not be
appropriate for storing this type of information, because it needs to
remain hidden. For this type of data, Kubernetes provides a Secrets
object, which we look at in the next section.
ConfigMaps | 43
Secrets
Kubernetes provides the Secrets construct for dynamic injection of
sensitive information into containers. As a best practice, sensitive
information such as user identification, passwords, and security
tokens should not be bundled directly into container images,
because this provides a greater opportunity for this sensitive infor‐
mation to be compromised. Instead, you should dynamically inject
sensitive information into containers in a Pod by using the Secrets
construct.
The Secrets construct works in a fashion that is very analogous to
ConfigMaps. Similar to ConfigMaps, you first create a Secret. After
you’ve created the Secret, Pods provide mechanisms to inject the
sensitive information that is stored in the Secret into a running con‐
tainer process. As an example, let’s assume that you have a username
and password that is needed by your container, and that the user‐
name is admin and the password is letbradin. To create a Secret
representing this information, you store both pieces of information
as text files and run the kubectl create secret command, as fol‐
lows:
$ echo -n “admin” > username.txt
$ echo -n “letbradin” > password.txt
$ kubectl create secret generic webserver-credentials
--from-file=./username.txt --
from-file=./password.txt
After you’ve created the Secret, there are two general ways for using
it: creating a file for each key value, and setting environment vari‐
ables.
To view the output from the container, use the kubectl logs <pod
Name> command, as follows:
$ kubectl logs secretexample-volume
You should then see the following output confirming that the keys
were created:
Secrets | 45
web_password
web_username
"letbradin"
To delete the Pod that you just created, run the following command:
$ kubectl delete pod secretexample-volume
Note that an env section has been added to the containers section.
The env section declares two keys: USER and PASS. Each of these will
be environment variables that are created. The USER environment
variable is assigned the value stored in username.txt of the
webserver-credentials Secret using the valueFrom and secret
KeyRef constructs. In a similar fashion, the PASS environment vari‐
able receives the value from password.txt in the webserver-
credentials Secret. As shown in the preceding example, you can
use the environment variables USER and PASS in the command that
To view the output from the container, use the kubectl logs <pod
Name> command, as follows:
$ kubectl logs secretexample
You should then see the following output confirming that the keys
were created:
user name: "admin" password: "letbradin"
$
To delete the Pod that you just created, run the following command:
$ kubectl delete pod secretexample
Image Registry
We’ve focused all of our attention thus far on the declarative resour‐
ces for Kubernetes—but where are containers in all of this?
You’ve already seen images referenced by the Pod:
...
image: nginx:1.7.9
...
But what is this statement? Let’s break it down. Every image refer‐
ence follows the form repository:tag. Let’s take a look at each of
these:
Image Registry | 47
repository
The repository reflects the logical name of an image. Typical
examples include nginx, ubuntu, or alpine. These repositories
are shorthand for docker.io/nginx, docker.io/ubuntu, and
docker.io/ubuntu. The Docker runtime knows to request the
bytes for these layers from the Docker Registry. You can also
store images in your own registry.
tag
This is a label that denotes a particular version. Tags can follow
any pattern that works for your delivery process. We recom‐
mend adopting semver, Git commit hashes, or a combination of
the two. You will also find the convention to use latest to denote
the most recent version available.
An actual image layer can have multiple tags and can be even be ref‐
erenced by multiple repository:tag combinations at the same time.
As you create a Continuous Integration/Continuous Delivery
(CICD) pipeline, you will be building images for all of the changes
to your application source code. Each image then is tagged and
pushed into an image registry, which your Kubernetes cluster can
access. Some options for private image registries include the follow‐
ing:
JFrog Artifactory
Artifactory is an artifact management tool. Artifactory supports
repositories for things like Java libraries (*.jar, *.war, *.ear) or
node modules along with your container images. Artifactory
offers both an open source and commercial version. With it,
you can extend your usage from build artifacts to Docker
images. You can deploy Artifactory via Helm charts in your
Kubernetes cluster as well, such as into your own IBM Cloud
Private Kubernetes cluster.
IBM Cloud Container Registry
An IBM-managed private container registry.
IBM Cloud Private built-in cluster registry
A built-in container registry available by default in your on-
premises Kubernetes cluster and tied to the same Role-Based
Access Control (RBAC) used for Namespaces.
Helm
We’ve talked about many kinds of Kubernetes resources that often
work in concert to deliver a complete application. When you want
to change values dynamically as part of a packaging or build step,
there is no built-in way to override parameter values on the com‐
mand line without editing the file. Helm is the Kubernetes package
manager that enables you to create a package containing multiple
templates for all of the resources that make up your application.
Common Kubernetes resources that you would expect to find
included in a Helm package are ConfigMaps, Deployments, Persis‐
tentVolumeClaims, Services, and many others.
A collection of templates is called a Helm chart. Helm provides its
own command-line interface (helm) to deploy a chart and provide
options for parameter values, either via command line or a single
file named values.yaml by convention.
IBM Cloud Private includes a rich catalog of Helm charts (see
Figure 3-1), which is available in the community edition, as well as
commercially supported versions.
Helm | 49
Figure 3-1. IBM Cloud Private provides a rich catalog of content,
delivered as Helm charts. Users can consume community charts or add
their own.
For users of IBM Cloud Kubernetes Service, you will find Helm con‐
tent easily browsable via the Solutions > Helm section of the User
Interface, as depicted in Figure 3-2.
Figure 3-2. IBM Cloud Kubernetes Service exposes Helm charts in the
catalog as well as providing a consistent packaging and deployment
capability for private and public clusters.
Next Steps
At this point we have covered a considerable amount of the key con‐
structs and high-level abstractions that Kubernetes provides. In
Chapter 4, we explore the creation of an enterprise-level production
application.
Next Steps | 51
CHAPTER 4
Introducing Our
Production Application
53
One of the Twelve Factors that enables scalable web services is the
externalization of configuration via the process environment. Many
aspects of Kubernetes can be directly mapped back to Twelve-Factor
principles. For example, as you’ve already seen in Chapter 3, Kuber‐
netes defines ConfigMaps and Secrets to provide configuration to
your app. Configuration data can take the form of process environ‐
ment variables, configuration files, or even Transport Layer Security
(TLS) certificates and keys.
A microservice is only as good as its dependencies; or, more pre‐
cisely, as good as how it anticipates and responds to failures within its
dependencies. If you are new to building microservices, we strongly
recommend reading Release It!, which introduces design patterns
such as Circuit Breaker, Bulkhead, Fail Fast, and Timeouts. Each of
these patterns focuses on addressing distributed systems behaviors
like chain reactions, cascading failures, and Service-Level Agree‐
ment (SLA) inversion. Each of these failure patterns is the result of
nested dependencies of microservices upon microservices upon
other microservices. The corresponding availability and stability
patterns (Circuit Breaker, Bulkhead, Fail Fast, etc.) are pragmatic,
language-agnostic approaches to responding gracefully with service
degradation rather than complete system outage.
We will now use an application built to demonstrate many aspects of
containerized applications, known as StockTrader. You can browse
the source code for this application at this book’s GitHub page.
Our portfolio application connects to several other services, includ‐
ing a database, messaging service, and other microservices. We take
advantage of various resources from Kubernetes to implement this
application. We’ve already talked about the declarative model of
Kubernetes. Let’s talk through the moving parts and then dig into
the details of how we apply a declarative model for our Stock Trader
application.
We are going to build our portfolio end to end, starting with ready-
to-go source code, building an image, and deploying the container
with Kubernetes. We’ll configure its dependencies and reuse images
for its dependent services which are already published and available
on DockerHub.
All of our containers are deployed as Pods. How these Pods are
managed by Kubernetes will be determined by their orchestration
controller. For instance, a Deployment creates a ReplicaSet, which
Namespaces
A Namespace in Kubernetes allows you to define isolation for
microservices. You can create a Namespace through YAML or the
command line. The following is a simple YAML file that describes
the stock-trader Namespace resource used by our Stock Trader
application:
apiVersion: v1
kind: Namespace
metadata:
name: stock-trader
Namespaces | 55
$ kubectl config set-context $(kubectl config current-context) \
--namespace=stock-trader
Kubernetes groups many of the concepts discussed earlier into
Namespaces. Namespaces allow you to isolate applications on your
cluster. With this isolation, you can control the following:
Role-Based Access Control (RBAC)
Defines what users can view, create, update, or delete within the
Namespace. We get into more details about how RBAC works in
Kubernetes in just a bit.
Network policies
Defines isolation to tightly manage incoming network traffic
(network ingress) and outgoing network traffic (network
egress) communication between Pods.
Quota management
Controls the resources allowed to be consumed by Pods within
the Namespace. Controlling quota allows you to ensure that
some teams do not overwhelm the capacity available within the
cluster.
Workload isolation by node
Namespaces can work with Admission Controllers to limit Pods
to running on specific nodes in your cluster.
Workload readiness or provenance
Namespaces can work with Admission Controllers to allow only
certain images (by whitelisting, image signatures, etc.) from
running in the context of the Namespace.
ServiceAccount
When you interact with your cluster, you often represent yourself as
a user identity. In the world of Kubernetes, you build intelligence
into the system to help it interact with its world. Many times, Pods
might use the Kubernetes API to interact with other parts of the sys‐
tem or to spawn work like Jobs. When we deploy a Pod, it might
interact with PersistentVolumes, the host filesystem, the host net‐
working, or be sensitive to what operating system (OS) user it is
given access to use for filesystem access. In most cases, you want to
restrict the default permissions for a given Pod from doing anything
more than the absolute basics. Basically, the less surface area that a
PodSecurityPolicy
When Pods are deployed into a Namespace, the ServiceAccount
affords them various privileges. A PodSecurityPolicy controls what
privileges are allowed. We will demonstrate how a PodSecurityPolicy
grants a container special OS permissions, including restricting spe‐
cific Linux kernel capabilities with the potential for very fine-
grained controls based on your organization’s security standards.
As we deploy our database, we will create a PodSecurityPolicy to be
used by a particular ServiceAccount to ensure that our database has
the required permissions to run in a container.
For users of IBM Cloud Kubernetes Service, you will find that Pod‐
SecurityPolicy is enabled by default. There are privileged-psp-
user and restricted-psp-user PodSecurityPolicy options, as well
as corresponding RBAC included in all clusters.1
PodSecurityPolicy | 57
healthy node. To ensure that our underlying storage is also highly
available, we use a distributed file system called GlusterFS.
clusterrole.rbac.authorization.k8s.io "db2-privileges-cluster\
-role" configured
clusterrolebinding.rbac.authorization.k8s.io "db2-privileges-\
cluster-role-binding"
created
$ export HELM_HOME=~/.helm>
$ export HELM_HOME=~/.helm
NAME: stocktrader-db2
LAST DEPLOYED: [...]
NAMESPACE: stock-trader-data
STATUS: DEPLOYED
RESOURCES:
==> v1/Service
NAME TYPE CLUSTER-IP \
EXTERNAL-IP PORT(S)
AGE
==> v1beta2/StatefulSet
NAME DESIRED CURRENT AGE
stocktrade-ibm-db2oltp-dev 2 2 2s
stocktrade-ibm-db2oltp-dev-etcd 3 3 2s
==> v1/Pod(related)
NAME READY STATUS RESTARTS AGE
stocktrade-ibm-db2oltp-dev-0 0/1 Pending 0 1s
stocktrade-ibm-db2oltp-dev-1 0/1 Pending 0 1s
stocktrade-ibm-db2oltp-dev-etcd-0 0/1 Pending 0 1s
stocktrade-ibm-db2oltp-dev-etcd-1 0/1 Pending 0 1s
stocktrade-ibm-db2oltp-dev-etcd-2 0/1 Pending 0 1s
==> v1/Secret
NAME TYPE DATA AGE
stocktrade-ibm-db2oltp-dev Opaque 1 2s
==> v1/PersistentVolumeClaim
NAME STATUS VOLUME CAPACITY ACCESS \
MODES STORAGECLASS AGE
stocktrade-hadr-stor Pending glusterfs 2s
NOTES:
1. Get the database URL by running these commands:
export NODE_PORT=$(kubectl get --namespace stock-trader- \
data -o jsonpath="{.spec.ports[0].nodePort}" \
services stocktrade-ibm-db2oltp-dev)
echo jdbc:db2://$NODE_IP:$NODE_PORT/sample
Regardless of which configuration you choose, you should find mes‐
sages in the logs of the container indicating that our database,
STRADER, was created:
...
(*) User chose to create STRADER database
(*) Creating database STRADER ...
DB20000I The CREATE DATABASE command completed successfully.
DB20000I The ACTIVATE DATABASE command completed successfully.
env
Our logic reuses some information where it can, such as the
password. Additional parameter values must match your envi‐
ronment, including the instance name and database name.
volumes
We reference a ConfigMap as a Volume in this example. All keys
which are known to the ConfigMap will appear as files to the
container, using the name of the key to define the name of the
file. We also set the defaultMode, to ensure that our initial script
will be executable.
configMap
The configMap defines our various files to be used in the initial‐
ization. If you are creating a ConfigMap from a file, you can
simplify the operation by way of the kubectl create command:
kubectl create configmap db2-createschema --from-file
db2-createschema.sh.
db2-setup.sh
The initial entry script which performs some initial configura‐
tion of the container and prepares the next script to run as the
db2inst1 user.
db2-createschema.sh
This connects to the remote database, catalogs it for remote
connections, and triggers the execution of SQL against the data‐
base.
1. Create the required Secrets. Many Secrets that are used (MQ,
ODM, Watson) are optional, which means the container can
start without them.
2. Build the image and push it to an image registry and create the
Image Pull Secret, if necessary, to deploy the portfolio microser‐
vice into the cluster.
3. Create the Deployment via kubectl.
Logging in to 'registry.ng.bluemix.net'...
Logged in to 'registry.ng.bluemix.net'.
OK
…
[INFO ] SRVE0169I: Loading Web Module: Portfolio.
[INFO ] SRVE0250I: Web Module Portfolio has been bound to
default_host.
[INFO ] SESN0172I: The session manager is using the Java
default SecureRandom
implementation for session ID generation.
[INFO ] SESN0176I: A new session context will be created
for application key
default_host/portfolio
[AUDIT ] CWWKT0016I: Web application available
(default_host): http://portfolio-
6b98585ff6-cx8sn:9080/portfolio/
…
Figure 4-2 depicts the user login page for the IBM Stock Trader
application. The default username and password are as follows:
• Username: stock
• Password: trader
With your new app, you can create, update, and delete portfolios. As
you interact with the trader web frontend, API calls are made to
portfolio, which, in turn, interacts with its Db2 database StatefulSet,
MQ messaging StatefulSet, and the loyalty and stock-quote microser‐
vices. Three example portfolios have been created in Figure 4-3. As
each of these portfolios is created, API calls are made from trader to
portfolio. In turn, portfolio updates the database to save the updates.
The total value of each portfolio is calculated based on results from
the stock-quote service via API calls as well.
Summary
In this chapter, we deployed a production-quality enterprise Kuber‐
netes application called IBM Stock Trader that is a composition of
Summary | 89
several microservices and uses numerous key Kubernetes resources
such as Deployments, StatefulSets, Services, and Secrets. In Chap‐
ter 5, we explore how to take advantage of Continuous Delivery
approaches for your Kubernetes applications.
91
Shift-left of operational practices
We should expose behaviors for health management, log collec‐
tion, change management, and so on earlier in the development
process.
Continuous integration of changes
All changes should be built and deployed together on an ongo‐
ing basis to identify when the intermingling of various changes
leads to an unforeseen issue or application programming inter‐
face (API) incompatibility.
Highly automated testing with continuous feedback
To manage velocity, you need to automate your testing and vali‐
dation work so that you can always be testing (ABT).
Image Build
Containers are the ideal unit of delivery because they encapsulate all
aspects of your application, middleware, and operating system (OS)
packages into a single package. You can create container images in
several ways, and the most popular means of creating an image is by
a Dockerfile. The Dockerfile describes all of the relevant details nec‐
essary to package the image. These instructions are just plain text
until converted by the image build, and you should always manage
these and other declarative resources we have learned about in
Kubernetes in a source control repository such as Git.
In Chapter 4, we built a container image. The act of building used
our Dockerfile as a set of instructions to construct the image. Let’s
take a closer look inside the Dockerfile:
FROM openliberty/open-liberty:microProfile1
FROM statements
They declare the foundation for your container. Generally, base
images are application runtimes (e.g., openliberty/open-
liberty:microProfile1 or node:latest) or operating systems (e.g.,
ubuntu:16.04, rhel:7, or alpine:latest).
RUN statements
They execute commands and save the resulting changes to the
filesystem as a distinct layer in the container image. To optimize
build time, move commands that need to adjust the container
image more frequently (e.g., adding application binaries)
toward the end of the file.
USER statements
They allow you to specify under what identity on the OS the
container should be launched. Building your container to run as
nonroot is considered best practice. Whenever specifying a user
for Kubernetes, we recommend that you use the UID (numeri‐
cal user ID from the OS) and create an alias via groupadd/user‐
add commands or equivalents for your OS.
COPY and ADD statements
They move files from your local workspace into the container
file system. COPY should always be your default; ADD is useful
when pulling directly from URLs, which is not supported by the
COPY directive.
Each line in the Dockerfile creates a unique layer that is reused for
subsequent builds where no changes have occurred. Reusing layers
creates a very efficient process that you can use for continuous inte‐
gration builds.
As you saw in Chapter 4, you build container images as follows:
$ docker build -t repository:tag .
The last period is significant because it denotes the current direc‐
tory. All files in the directory are shipped off to the Docker runtime
to build the image layers. You can limit files from being delivered by
adding a .dockerignore file. The .dockerignore file specifies
files by name pattern to either be excluded (the default) or included
(by beginning the line with an exclamation mark [!”]).
Image Build | 93
Programmability of Kubernetes
The declarative model of Kubernetes objects makes them ideal for
source control, meaning that you have a record (throughout the his‐
tory of your source control system) of all changes that were made
and by whom.
Using the command kubectl apply, you can push updates easily
into your cluster. Some resource types do not easily support rolling
or uninterrupted updates (like DaemonSets), but a majority do. You
don’t need to stop at just one object, though; you can apply entire
directories:
$ kubectl apply -Rf manifests/
97
ized logging solution that will aggregate logs from their distributed
applications to prevent the need to chase down dozens or hundreds
of Pods using kubectl logs -f --tail=1000 mypodname.
With microservices, you break down your core functions into
smaller building blocks, which means that your logged information
is going to be more decentralized. There are several common pat‐
terns for collecting logs from containerized platforms.
First, you need a way to collect log information from your contain‐
ers. Let’s take a look at a couple of tools that can help you with this:
Logstash
Logstash is a very well-established log aggregator with the capa‐
bilities to collect logs, parse, and then transform them to meet
your standards.
Fluentd
Fluentd is an open source data collector that provides an inde‐
pendent and unifying layer between different types of log inputs
and outputs. Fluentd was recently contributed to the Cloud
Native Computing Foundation (CNCF). Although Fluentd is
younger than Logstash, its alignment with the CNCF has driven
its increasing popularity.
Both of these options provide a robust way to collect logs from mul‐
tiple sources—from containers to nodes—and then ship them into a
searchable datastore.
After your logs are aggregated, you need a place to store them for
discovery and analysis. Elasticsearch is the de facto standard for col‐
lecting, indexing, and providing an ongoing store for plain-text or
JSON log output. Elasticsearch provides several key features that
make it a great option for your log data:
JSON-based query language, accessible via REST API
You can consume information from Elasticsearch easily for dis‐
covery and analysis. Often, Elasticsearch ends up being part of
machine learning applications because of its simple but power‐
ful model of querying and updates.
Log retention policy control
You might need to keep only a week of logs for postmortems
when bad things happen. In other cases, compliance or audit
requirements might require you to keep logs for months or
1 Ranjit Mavinkurve, Justin Becker, and Ben Christensen, “Improving Netflix’s Opera‐
tional Visibility with Real-Time Insight Tools”, Medium.com.
Figure 6-5. Data for IBM Cloud Monitoring is collected via a custom
Fluentd plug-in (cAdvisor). No additional configuration or agent is
required.
Several dashboards are available out of the box. You can always
browse community dashboards available for Prometheus and add
your own. Figure 6-7 demonstrates available dashboards in IBM
Cloud Private out of the box. You can create additional dashboards
to fit your needs or import them from the community website and
customize them as you need.
Figure 6-7. Available dashboards out of the box in IBM Cloud Private
2.1.0.3.
109
ations teams will encounter. Although Kubernetes on its own is
capable of helping to simplify many of the concerns these teams
might have, there are still some significant complexities that are
unique to hybrid cloud environments.
A hybrid cloud environment is ideal for companies that are making
the transition from on-premises to a public cloud. Many organiza‐
tions initially begin by using public cloud environments for their
development needs, and in this domain compliance and data resi‐
dency issues are not a factor. Over time, most teams become accus‐
tomed to the simplified operations and reduced cost of the public
cloud and they begin the journey of migrating other components,
such as their stateless applications and auxiliary services, as well. In
this chapter, we provide an overview of a variety of cluster opera‐
tions and hybrid cloud–related issues that are typically encountered
in enterprise environments.
Access Control
Access control consists of concepts and features relating to how you
authenticate and authorize users for Kubernetes. There are any
number of operational and hybrid considerations to account for.
The following sections describe operational and hybrid considera‐
tions for both authentication and authorization.
Authentication
Users and operators will encounter a wide variety of authentication
solutions for Kubernetes. There could be multiple authentication
methods in use simultaneously in different portions of a hybrid
cloud environment:
Client certificates
X.509 client certificates are Secure Sockets Layer (SSL) certifi‐
cates. The common name of the certificate is used to identify
the user. The organization of the certificate is used to identify
group membership. Often, this is used as the “superuser”
authentication method.
Static tokens
These are statically defined bearer tokens that are passed in dur‐
ing apiserver startup. In general, they are not commonly used.
Hybrid
The key challenge that you must address is how to provide access
control in a hybrid environment. There are several different feasible
approaches that are possible in hybrid environments; two key
approaches that we describe here are federated identity and imperso‐
nation.
Federated identity
Using a federated-identity provider might be the most straightfor‐
ward solution for centralized identity management. This approach
permits the centralized management of users and identity. Many
enterprise customers already have experience with identity federa‐
tion for other services.
Impersonation
You might want to consider an option that uses identity impersona‐
tion. This concept has an operator build a proxy that does authenti‐
cation against an existing identity provider. The proxy then uses a
cluster-admin identity native to the target Kubernetes cluster and
passes impersonation headers on each request. Here’s an example:
1. Bob uses kubectl to make a request against the proxy for clus‐
ter A.
2. The proxy authenticates Bob using an identity provider. Bob is
identified as bob and belongs to groups teama and teamb.
3. The proxy authenticates with cluster A using X.509 certificates
or other method and passes impersonation headers:
Impersonate-User: [email protected]
Impersonate-Group: developers
Impersonate-Group: admins
4. Cluster A authorizes the request based on the impersonate user
and groups.
5. The same flow can be used against cluster B.
Scheduling
Understanding how the Kubernetes scheduler makes scheduling
decisions is critical in order to ensure consistent performance and
optimal resource utilization. All scheduling in Kubernetes is done
based upon a few key pieces of information. First, it is using the
information about the worker Node to decide what the total capacity
of the Node is. Using kubectl describe node <node> will give you
all the information you need to understand regarding how the
scheduler sees the world, as is demonstrated here:
Capacity:
cpu: 4
ephemeral-storage: 103079200Ki
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 16427940Ki
pods: 110
Allocatable:
cpu: 3600m
ephemeral-storage: 98127962034
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 14932524020
pods: 110
In this example, we see what the scheduler sees as being the total
capacity of the worker Node, as well as the allocatable capacity. The
allocatable numbers factor in kubelet settings for Kubernetes and
Autoscaling
There are four common forms of autoscaling in use today, falling
into two categories: monitoring-based autoscalers (Horizontal Pod
Autoscalers and Vertical Pod Autoscalers), and cluster scale–based
We can then view the resource requests for our hello Deployment
and see that they have been automatically adjusted to match real-
world utilization of our application:
resources:
requests:
cpu: 80m
memory: 50Mi
addon-resizer
Another great example is the addon-resizer (aka pod_nanny), which
performs vertical scaling of resource requests based upon cluster
size. It scales the resource’s requests of a singleton based on the
number of workers in the cluster. This autoscaler has been used by
heapster:
- command:
- /pod_nanny
- --config-dir=/etc/config
- --cpu=80m
- --extra-cpu=0.5m
- --memory=140Mi
- --extra-memory=4Mi
- --threshold=5
- --deployment=heapster
- --container=heapster
- --poll-period=300000
- --estimator=exponential
Networking
Kubernetes networking is both simple and complex. Simplicity
comes from the concept that Kubernetes itself is doing very little
networking magic. The complexity comes from the various net‐
working plug points and concepts that are a part of Kubernetes.
Here are the core Kubernetes networking concepts:
Pod Networking
The most central networking component of Kubernetes is the Pod
networking. This is what makes each Pod (and the containers within
it) network addressable. Kubernetes itself implements only the most
basic networking for Pods. This is the Pod network Namespace,
which is shared by all containers of the Pod. The Pod network
Namespace allows all the containers of the Pod to communicate
with one another as if they were both running in their own dedica‐
Networking | 123
ted host. Container A is able to communicate with Container B via
localhost. All containers within the Pod share a single port space,
just as two processes on one compute host must contend for avail‐
able ports. The Pod network Namespace is provided by the Kuber‐
netes pause container, which does nothing other than create and
own the network Namespace.
Next up is the Container Network Interface (CNI) plug-in that con‐
nects the Pod network Namespace to the rest of the Pods in the
Kubernetes cluster. There are a huge number of different CNI plug-
ins available for various network implementations. See the official
documents for more on CNI plug-ins. The most important thing to
know is that the cluster networking is what provides Pod-to-Pod
communication within the cluster and IP address management.
Services/kube-proxy/Load Balancers
Kubernetes leans heavily upon the concept of microservices; the
idea being that there are many services within the cluster that pro‐
vide functional units and are accessed from within the cluster or
from outside the cluster as Services. Much has been written on the
topic, and the Kubernetes documentation details Services. The most
common use case for Services is to create a Kubernetes Deployment
or ReplicaSet and expose that group of Pods as a single, scalable,
highly available Service.
The kube-proxy component is what makes all of this possible. We
won’t go into the details of the kube-proxy mechanics here, but,
essentially, it provides very efficient and highly available load bal‐
ancing of Kubernetes Services within the cluster.
Finally, we have load balancers, which are a concept within Kuber‐
netes that allow access to a Kubernetes Service from outside the
cluster. When creating a service of type=LoadBalancer, you are
indicating that the service should be externally available. Your
Kubernetes Service provider or bare-metal solution will determine
exactly the implementation details of the load balancer. In all cases,
you end up with an external IP address, which can be used to access
the Service. You can find more details in the Kubernetes documenta‐
tion.
Of Services, kube-proxy, and load balancers, hybrid considerations
typically arise only for load balancers. This is because in many cases
Ingress
Ingress provides a Kubernetes object model to make Services avail‐
able outside the Kubernetes cluster via a Layer 7 application load
balancer. An example would be providing public internet access to a
web service or web page. Your Ingress controller of choice will
determine the exact implementation. You need only create an
Ingress object to indicate the desire to expose a Kubernetes Service
externally via an application load balancer. Here’s an example object:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: hello-ingress
spec:
rules:
- host: kitch.us-east.containers.appdomain.cloud
http:
paths:
- path: /
backend:
serviceName: hello
servicePort: 80
This indicates to the Ingress controller that the hello Service should
be exposed on the kitch.us-east.containers.appdomain.cloud
hostname. It is then up to the Ingress controller to realize this
desired state.
Hybrid strategies are heavily affected by Ingress because the
Ingress controller of each provider supports a wide variety of anno‐
tations that are associated with each implementation. Because of this
variability between providers, the Ingress objects are often man‐
aged by the operations team rather than the development team. In
this scenario, the developers might request to have a specific Service
exposed via a specific Ingress; the operators can then create the
Ingress object, and the development team is free to make changes
and update the backend service implementation at any time. One
option to provide consistency between Kubernetes clusters is to use
a community Ingress controller with a Kubernetes Load Balancer
Networking | 125
Service. To do this, you would deploy an Ingress controller such as
the Kubernetes ingress-nginx with multiple replicas and then
expose it by using a Service such as:
$ kubectl expose deploy ingress-nginx --port 443 --type
LoadBalancer
Network Security/Policy
There are two levels of network security that we need to discuss as it
relates to Kubernetes: securing the worker Nodes and securing the
Pods. Securing Pods in Kubernetes is the domain of Kubernetes Net‐
workPolicy, which, when used with a CNI plug-in that supports pol‐
icy, allows users and operators to control network access controls at
the Pod level. It’s also possible to use Istio for Pod network policy,
which we discuss in “Istio” on page 129. Network Security of the
worker Nodes is not the domain of Kubernetes. You must secure the
Nodes with external tools such as a Virtual Private Cloud (VPC),
network Access Control List (ACL), or security groups from your
cloud provider, iptables, or a cloud security solution such as Project
Calico.
NetworkPolicy
NetworkPolicy objects are used to control networking traffic enter‐
ing and leaving Kubernetes Pods. Entering network traffic is com‐
monly referred to as ingress traffic, and networking traffic leaving
the Pod is commonly referred to as egress traffic. The NetworkPo
licy object is quite flexible and its capabilities have grown consider‐
ably over time. We won’t go into the details of how to construct
Networking | 127
desired solution for hybrid scenarios because some services might
be hosted publicly, but operators might desire to allow only on-
premises users to access the service. We can use our default-deny
policy to get started. Now we want to allow our corporate network
(159.27.17.32/17) to access our HR application:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-corporate-to-hr-frontend
namespace: teama
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- ipBlock:
cidr: 159.27.17.32/17
- podSelector:
matchLabels:
app: hr-frontend
ports:
- protocol: HTTP
port: 8080
Istio
We talk only briefly about Istio here, given that we could write an
entire book on this topic alone. In this section, we do provide a
short overview of the features of Istio so that you can make your
own decision as to whether it is worth exploring this framework in
greater depth.
At its heart, Istio is a service mesh framework that provides some of
the same microservices features that you will find in Kubernetes
itself, such as service discovery and load balancing. In addition, Istio
brings with it traffic management, service identity and security, pol‐
icy enforcement, and telemetry. Two Istio concepts that operators
might find particularly appealing are mutual TLS, to secure Pod-to-
Pod traffic using centrally managed certificates, and egress policy.
Egress policy in Istio is especially appealing because it removes the
need to create policy based on external service Classless Inter-
Domain Routing (CIDR) blocks and allows policy definitions based
Networking | 129
on URL. Allowing access to an external Compose MongoDB service
is as simple as doing the following:
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: compose-mongodb
spec:
hosts:
- sl-us-south-1-portal.27.dblayer.com
- sl-us-south-1-portal.28.dblayer.com
ports:
- number: 47400
name: mongodb
protocol: TCP
This policy allows all of our Pods and Services running in the
Kubernetes cluster to have egress access to our publicly hosted mon
godb Service. This allows you to access this Service regardless of
whether it might be hosted behind a Content Delivery Network
(CDN) or other proxy-based solution. No more chasing IPs.
There is a large array of operational and architectural problems that
are neatly solved by Istio. We strongly recommend that you investi‐
gate it as your use of Kubernetes grows and becomes more
advanced.
Storage
Earlier, we spoke about some of the storage concepts in Kubernetes,
including Volumes, PersistentVolumes (PVs), and PersistentVolu‐
meClaims (PVCs). From an operations standpoint, there are not too
many issues that need to be addressed. One consideration is how the
storage is managed. In some shops, there might be a storage admin
who has the proper RBAC to create PVs and PVCs. The developer
only has access to refer to the PVCs. Of course, a trusting admin
might provide RBAC for developers to create their own PVCs
directly, especially in environments that support dynamic storage
provisioning because it dramatically simplifies the ability to rapidly
deploy and scale applications that are dependent on storage.
One area where storage knowledge is critical is when dealing with
StorageClasses. StorageClasses are used to specify the perfor‐
mance and other characteristics of the backing physical storage. In
hybrid environments, you are certain to see variability in Storage
Classes, and here is where a knowledgeable storage admin and per‐
formance engineer can help to find and create comparable classes
for use across hybrid clusters. This allows the developer to get con‐
sistent performance characteristics.
Storage | 131
details are available in the IBM Cloud Kubernetes Service documen‐
tation.
GlusterFS
GlusterFS is a distributed filesystem that can be exposed to Kuber‐
netes. GlusterFS is an example of software-defined storage, in which
arbitrary block storage devices (called bricks) have a replicated file‐
system managed across them. The advantage of course is that if
Nodes that are hosting a portion or replica of the filesystem fail,
other Nodes will still be able to access the data from surviving repli‐
cas. There are a few abstraction layers to be aware of, so let’s go
through them.
Quotas
Resource quotas are a critical aspect whenever managing shared
resources. Kubernetes allows you to control resource allocations at
many levels, including fine-grained controls per Pod such as
Quotas | 133
requests.memory
The amount of memory to reserve on the cluster. You may
express units as you do with limit.memory.
services
A count of the total Services allowed in the Namespace.
persistentvolumeclaims
A count of the total PVCs allowed in the Namespace.
The most important thing to remember is to set requests for quotas
and Pods. Requests ensure that sufficient capacity is reserved and is
critical for effective, balanced scheduling of Pods across the nodes in
your cluster. More details on requests are provided in “Perfor‐
mance, Scheduling, and Autoscaling” on page 116.
The previous example shows services and persistentvolume
claims, but most Kubernetes resources can be expressed to provide
an absolute limit on the number of these kinds of resources allowed
in the Namespace. We express these two because we find they are
the most important to limit.
Note that resourcequota is its own kind and it is Namespace
scoped. Hence, the quota that governs a Namespace is defined
within the Namespace. When you define Kubernetes Roles and Role
Bindings, be sure to limit the ability to create and modify resource
quota objects to only the administrators or operators who govern
control of the cluster. Otherwise, you might expose yourself to an
enterprising developer who wants more capacity than intended.
You apply quotas like all other resources in Kubernetes:
$ kubectl apply -f resource-quota.yaml
resourcequota "stock-trader-quota" created
If you are using IBM Cloud Private, you can also use the web con‐
sole to make it easier to create resource quotas, under Manage >
Quotas. Figure 7-1 demonstrates the creation of a resource quota for
the stock-trader Namespace using the tools provided by IBM
Cloud Private.
Kubernetes Website
The primary Kubernetes website is an excellent place to begin look‐
ing for information on Kubernetes. As shown in Figure 8-1, the
home page for Kubernetes provides links for more information on
topics such as documentation, community, blogs, and case studies.
In the community section of the Kubernetes website, you will find
137
more information on how to join the large number of Kubernetes
Special Interests Groups (SIGs). Each SIG focuses on a specific
aspect of Kubernetes, and hopefully you can find a group that
excites you and matches your interests.
143
become possible when embracing a Kubernetes container-based
development methodology.
1 Schmidt BK, Sunderam VS., (1994). “Empirical Analysis of Overheads in Cluster Envi‐
ronments,” Concurrency Practice & Experience, 6: 1–32.
Conclusions
In this book, we have covered a broad number of Kubernetes topics.
We provided a historical overview of the rise of both containers and
Kubernetes and the positive impact of the Cloud Native Computing
Foundation. We described the architecture of Kubernetes, its core
concepts, and its more advanced capabilities. We then walked
through an enterprise-level production application and discussed
approaches for continuous delivery. We then explored operating
applications in enterprise environments with a focus on log collec‐
tion and analysis, and health management. We also looked at Kuber‐
netes cluster operations and hybrid cloud–specific considerations
and issues. Finally, we presented several resources that are available
Kubernetes Will Become the de Facto Platform for Machine Learning and Deep Learning
Applications | 145
to help you become a contributor to the Kubernetes community,
and we ended with a short discussion on what the future holds for
Kubernetes. We hope that you have found this book helpful as you
begin your journey of deploying enterprise quality Kubernetes
applications into production environments, and hope it accelerates
your ability to fully exploit Kubernetes-based hybrid cloud environ‐
ments.
147
git clone https://github.com/IBM/deploy-ibm-cloud-private.git
cd deploy-ibm-cloud-private
Open the Vagrantfile and customize it for your machine’s capacity:
# Vagrantfile
...
# most laptops have at least 8 cores nowadays (adjust based
# on your laptop hardware)
cpus = '2'
# use this setting for better performance if you have the ram
# available on your laptop
# uncomment the below line and comment out the above line
# "#memory = '4096'"
memory = '10240'
…
Now, just bring up the Vagrant VirtualBox machine. As it comes up,
IBM Cloud Private will be configured using the Community Edition
available on DockerHub:
vagrant up
Password>
Authenticating...
OK
Select an account:
1. mycluster Account (id-mycluster-account)
Enter a number> 1
Targeted account mycluster Account (id-mycluster-account)
OK
Follow the prompts to enter your password and select your cluster.
Confirm that you now have access by running a command with
kubectl, such as the following:
kubectl get pods
Configuring Java
Java provides a robust, enterprise-grade language for the develop‐
ment of all kinds of applications. To build Java applications from
source, you need a Java Software Development Kit (Java SDK). To
run Java applications, which are compiled into Java Archives (*.jar,
*.war, *.ear), you need a Java Runtime Environment (JRE).
There are many options for Java. We recommend IBM’s Java SDK.
Configuring Maven
Apache Maven is a build tool that is very popular for Java applica‐
tions. You can download and configure Maven from Apache’s web‐
site.
Configuring Docker
The examples in this book use Docker to create Open Container Ini‐
tiative (OCI)-compatible images. Docker runs OCI-compliant
images and provides an easy-to-use API and tools for working with
these images. You can configure Docker for your platform from
Docker’s website.
151
APPENDIX C
Configuring Docker to Push or Pull
from an Insecure Registry
153
"insecure-registries" : [
"mycluster.icp:8500"
],
"experimental" : true
}
Then, update your /etc/hosts configuration to alias this hostname
(provided by the certificate when Docker connects to the endpoint)
to the specific public IP of your cluster:
cat /etc/hosts | grep mycluster.icp
1.1.1.1mycluster.icp
Restart your Docker runtime to make this change effective.
To find more details for your platform, refer to the Docker docs.
155
About the Authors
Michael Elder is an IBM Distinguished Engineer. He provides tech‐
nical leadership and oversight of IBM Private Cloud Platform with a
strong focus on Kubernetes and enterprise requirements.
Jake Kitchener is an IBM Senior Technical Staff Member (STSM)
and provides technical leadership for the IBM Cloud Kubernetes
Service. His focus is on user experience, scalability, availability, and
system architecture.
Dr. Brad Topol is an IBM Distinguished Engineer leading efforts
focused on open technologies and developer advocacy. Brad is a
Kubernetes contributor, serves as a member of the Kubernetes Con‐
formance Workgroup, and is a Kubernetes documentation main‐
tainer. He received a PhD in Computer Science from the Georgia
Institute of Technology in 1998.