Container images

The first step for using Kubernetes is getting your software into a container. Docker is the easiest way to create these containers, and it is a fairly simple process. Let's take a moment to look at an existing container image to understand what choices you will need to make when creating your own containers:

docker pull docker.io/jocatalin/kubernetes-bootcamp:v1

First, you'll see it pulling down a list of files with arcane IDs. You'll see them updating in parallel, as it tries to grab these as they're available:

v1: Pulling from jocatalin/kubernetes-bootcamp
5c90d4a2d1a8: Downloading 3.145MB/51.35MB
ab30c63719b1: Downloading 3.931MB/18.55MB
29d0bc1e8c52: Download complete
d4fe0dc68927: Downloading 2.896MB/13.67MB
dfa9e924f957: Waiting

And when the downloads are complete, the output will update to say extracting, and finally pull complete:

v1: Pulling from jocatalin/kubernetes-bootcamp
5c90d4a2d1a8: Pull complete
ab30c63719b1: Pull complete
29d0bc1e8c52: Pull complete
d4fe0dc68927: Pull complete
dfa9e924f957: Pull complete
Digest: sha256:0d6b8ee63bb57c5f5b6156f446b3bc3b3c143d233037f3a2f00e279c8fcc64af
Status: Downloaded newer image for jocatalin/kubernetes-bootcamp:v1

What you saw in the Terminal was Docker downloading the layers that go into a container image, pulling them all together, and then verifying the output. Kubernetes does exactly this same process when you ask it to run the software, downloading the images and then running them.

If you now run the following:

docker images

You will see (perhaps among others) the image listed akin to this:

REPOSITORY                                         TAG                 IMAGE ID            CREATED             SIZE
jocatalin/kubernetes-bootcamp v1 8fafd8af70e9 13 months ago 211MB

The image is 211MB in size, and you'll notice that when we specified jocatalin/kubernetes-bootcamp:v1, we were specifying both a name, jocatalin/kubernetes-bootcamp, and a tag, v1. In addition, the image has an IMAGE ID (8fafd8af70e9), which is a unique ID for the whole image. If you were to specify a name for an image without a tag, the default is to assume you want a default tag of latest.

Let's take a deeper look at the image we just downloaded, using the docker history command:

docker history jocatalin/kubernetes-bootcamp:v1
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
8fafd8af70e9 13 months ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "no... 0B
<missing> 13 months ago /bin/sh -c #(nop) COPY file:de8ef36ebbfd53... 742B
<missing> 13 months ago /bin/sh -c #(nop) EXPOSE 8080/tcp 0B
<missing> 13 months ago /bin/sh -c #(nop) CMD ["node"] 0B
<missing> 13 months ago /bin/sh -c buildDeps='xz-utils' && set... 41.5MB
<missing> 13 months ago /bin/sh -c #(nop) ENV NODE_VERSION=6.3.1 0B
<missing> 15 months ago /bin/sh -c #(nop) ENV NPM_CONFIG_LOGLEVEL=... 0B
<missing> 15 months ago /bin/sh -c set -ex && for key in 955... 80.8kB
<missing> 15 months ago /bin/sh -c apt-get update && apt-get insta... 44.7MB
<missing> 15 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 15 months ago /bin/sh -c #(nop) ADD file:76679eeb94129df... 125MB

This is making explicit what we saw earlier when it downloaded the container: that a container image is made up of layers, which build upon the ones below it. The layers of a Docker image are quite simple—each layer is the result of a command being executed and any changes that the command ended up making on the local filesystem. In the previous docker history command, you will see a size reported by any commands that changed the size of the underlying filesystem.

The image format was created by Docker and is now formally specified by the OCI (Open Container Initiative) Image Format project. If you want to dig into that further, you can find the format and all relevant details at https://github.com/opencontainers/image-spec.

Container images, and each of the layers in the images, are typically available on the internet. All the examples I use in this book are publicly available. It is possible to configure your Kubernetes cluster to use a private image repository, and there's documentation at the Kubernetes project for exactly how to do that task, available at https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/. This setup is more private, but at the cost of being more involved and complex to set up, so in this book, we will be sticking with publicly available images.

A container image also includes information on how to run the image, what to run, what environment variables to set, and so forth. We can see all those details using the docker inspect command:

docker inspect jocatalin/kubernetes-bootcamp:v1

The preceding command produces quite a bit of content, describing the container image in quite a bit of detail and the metadata that goes along with running the code within it:

[
{
"Id": "sha256:8fafd8af70e9aa7c3ab40222ca4fd58050cf3e49cb14a4e7c0f460cd4f78e9fe",
"RepoTags": [
"jocatalin/kubernetes-bootcamp:v1"
],
"RepoDigests": [
"jocatalin/kubernetes-bootcamp@sha256:0d6b8ee63bb57c5f5b6156f446b3bc3b3c143d233037f3a2f00e279c8fcc64af"
],
"Parent": "",
"Comment": "",
"Created": "2016-08-04T16:46:35.471076443Z",
"Container": "976a20903b4e8b3d1546e610b3cba8751a5123d76b8f0646f255fe2baf345a41",
"ContainerConfig": {
"Hostname": "6250540837a8",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"8080/tcp": {}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"NPM_CONFIG_LOGLEVEL=info",
"NODE_VERSION=6.3.1"
],
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"/bin/sh\" \"-c\" \"node server.js\"]"
],
"ArgsEscaped": true,
"Image": "sha256:87ef05c0e8dc9f729b9ff7d5fa6ad43450bdbb72d95c257a6746a1f6ad7922aa",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": [],
"Labels": {}
},
"DockerVersion": "1.12.0",
"Author": "",
"Architecture": "amd64",
"Os": "linux",
"Size": 211336459,
"VirtualSize": 211336459,

In addition to the base configuration, a Docker container image can also contain a runtime configuration, so there is often a duplicate section defining much of what you say under the ContainerConfig key:

        "Config": {
"Hostname": "6250540837a8",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"8080/tcp": {}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"NPM_CONFIG_LOGLEVEL=info",
"NODE_VERSION=6.3.1"
],
"Cmd": [
"/bin/sh",
"-c",
"node server.js"
],
"ArgsEscaped": true,
"Image": "sha256:87ef05c0e8dc9f729b9ff7d5fa6ad43450bdbb72d95c257a6746a1f6ad7922aa",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": [],
"Labels": {}
},

The last section included is an explicit list of the overlays for filesystems and how they fit together:

"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/b38e59d31a16f7417c5ec785432ba15b3743df647daed0dc800d8e9c0a55e611/diff:/var/lib/docker/overlay2/792ce98aab6337d38a3ec7d567324f829e73b1b5573bb79349497a9c14f52ce2/diff:/var/lib/docker/overlay2/6c131c8dd754628a0ad2c2aa7de80e58fa6b3f8021f34af684b78538284cf06a/diff:/var/lib/docker/overlay2/160efe1bd137edb08fe180f020054933134395fde3518449ab405af9b1fb6cb0/diff",
"MergedDir": "/var/lib/docker/overlay2/40746dcac4fe98d9982ce4c0a0f6f0634e43c3b67a4bed07bb97068485cd137a/merged",
"UpperDir": "/var/lib/docker/overlay2/40746dcac4fe98d9982ce4c0a0f6f0634e43c3b67a4bed07bb97068485cd137a/diff",
"WorkDir": "/var/lib/docker/overlay2/40746dcac4fe98d9982ce4c0a0f6f0634e43c3b67a4bed07bb97068485cd137a/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:42755cf4ee95900a105b4e33452e787026ecdefffcc1992f961aa286dc3f7f95",
"sha256:d1c800db26c75f0aa5881a5965bd6b6abf5101dbb626a4be5cb977cc8464de3b",
"sha256:4b0bab9ff599d9feb433b045b84aa6b72a43792588b4c23a2e8a5492d7940e9a",
"sha256:aaed480d540dcb28252b03e40b477e27564423ba0fe66acbd04b2affd43f2889",
"sha256:4664b95364a615be736bd110406414ec6861f801760dae2149d219ea8209a4d6"
]
}
}
]

There's a lot of information in that JSON dump, more than you probably need or care about right now. Most importantly, I want you to know that it specifies a cmd under the config section in three parts. This is what will be invoked by default if you run the container, and it is often called the Entrypoint. If you put those pieces together and imagine running them yourself in the container, you would be running the following:

/bin/sh -c node server.js

The Entrypoint defines what binary will get executed, and any arguments to it, and is the key to specify what you want to run and how you want to run it. Kubernetes works with this same Entrypoint and can override it, with commands and arguments to run your software, or run diagnostic tools that you have stored in the same container image.