SANDEEP DINESH: The first step in deploying to Kubernetes
is putting your app inside a container.
But why stop there?
In this episode of "Kubernetes Best Practices,"
let's explore how you can create small and secure container
images.
[MUSIC PLAYING]
Thanks to Docker, creating container images
has never been simpler.
Specify your base image.
Add your changes, and build your container.
While this is great for getting started, using the default
base images can lead to large images full of security
vulnerabilities.
Most Docker images use Debian or Ubuntu as the base image.
While this is great for compatibility
and easy onboarding, these base images
can add hundreds of megabytes of additional overhead
to your container.
For example, simple Node.js and Go,
"hello world" apps are around 700 megabytes.
Your application is probably only a few megabytes in size.
So all this additional overhead is
wasted space and a great hiding place
for security vulnerabilities and bugs.
So let's look at two methods to reduce the container image
size--
using small base images and using the builder patcher.
Using smaller base images is probably the easiest way
to reduce your container size.
Chances are your language or stack that you are using
provides an official image that's
much smaller than the default image.
For example, let's take a look at our Node.js container.
Going from the default node:8 to node:8-alpine reduces our base
image size by 10 times.
To move to a smaller base image, update your Docker
file to start with a new base image.
Now, unlike the old onbuild image,
you need to copy your code into the container
and install any dependencies.
In the new Docker file, the container starts with
the node:alpine image, creates a directory for the code,
install dependencies with NPM, and finally,
starts the Node.js server.
With this update, the resulting container
is almost 10 times smaller.
If your programming language or stack
doesn't have an option for a small base image,
you can build your container using raw Alpine
Linux as a starting point.
This also gives you complete control
over what goes inside your containers.
Now, using a small base image is a great way
to quickly build small containers.
But you might be able to go even smaller using the builder
pattern.
With interpretive languages, the source code
is sent to an interpreter, and then it is executed directly.
But with a compiled language, the source code
is turned into compiled code beforehand.
Now, with compile languages, the compilation step
often requires tools that are not needed
to actually run the code.
So this means that you can remove these tools
from the final container completely.
To do this, you can use the builder pattern.
The code is built in the first container,
and then the compiled code is packaged in the final container
without all the compilers and tools required
to make the compiled code.
So let's take a Go application through this process.
First, let's move from the onbuild image to Alpine Linux.
In the new Docker file, the container starts with
a golang:alpine image.
Then it creates a directory for the code, copies in the source
code, builds the source, and then finally starts the app.
This container is much smaller than the onbuild container,
but it still contains a compiler and other Go tools
that we really don't need.
Let's extract just the compiled program out and put it
into its own container.
So you might notice something strange about this Docker file.
It has two FROM lines.
The first section looks exactly the same as the previous Docker
file, except that it uses the AS keyword
to give this step a name.
In the next section, there is a new FROM line.
This will start a fresh image, and instead of using
golang:alpine, we will use raw alpine as the base image.
Raw Alpine Linux doesn't have any SSL certificates installed,
which will make most API calls over HTTPS fail.
So let's install some root CA certificates.
And now comes the interesting part.
You can use the COPY command to copy the compiled code
from the first container into the second.
This line will copy just that one file and not
the rest of the Go tooling.
This new multistage Docker file contains a container image
that's just 12 megabytes.
The original container image was 700 megabytes.
That is quite a difference.
Using small base images and the builder pattern
are great ways to create much smaller containers
without a lot of work.
Now, depending on your application stack,
there may be additional ways to reduce your container image
size as well.
But do small containers actually have a measurable advantage?
Let's look at two areas where small containers shine--
performance and security.
For performance, let's look at how long
it takes to build a container, push it to a registry,
and then pull it down from the registry.
For the initial build, you can see
that the smaller container has a huge advantage over larger
containers.
Docker will cache layers so subsequent builds will take
very little time for either.
But for many CI systems that folks use to build and test
containers, they don't cache layers,
so there is a significant time saving here.
Just think about how many times you're building and testing
your code.
Now that the container is built, you
need to push it to a container registry
so you can use it in your Kubernetes cluster.
I recommend using the Google Container Registry.
You only pay for the raw storage and network.
There is no additional fee to manage containers.
It's private and secure, and it's lightning fast.
In fact, GCR uses many tricks to speed up pushing.
You can see that the time to push
both the containers for the large machine
is almost the same.
This is because GCR uses a global cache for common base
images, meaning you don't need to upload them at all.
With the small machine, the CPU becomes the bottleneck.
As you can see, there is still a significant advantage
to use small containers.
If you're using Google Container Registry,
I highly recommend using Google Container Builder
as part of your build system.
As you can see, it's much faster to build and push
than even the large machine.
And you get a 120 build minutes free per day,
which should be enough to cover most people's
container-building needs.
Now comes the most important performance metric--
pulling the container.
While you might not care about the time it takes
to build and push a container, you
should really care about the time
it takes to pull the container.
For example, let's say you have a three node cluster and one
of the nodes crashes.
If you're using a managed system like Google Kubernetes Engine,
the system will automatically spin up
a new node to take its place.
However, this new node will be completely fresh
and will have to pull all your containers
before it can start working.
If it takes too long to pull the containers,
this is just time where your cluster isn't performing
as well as it should.
Now, there are many cases where this may occur,
such as adding a new node to your cluster,
upgrading your nodes, or even switching
to a new container for your deployments.
So minimizing pull times becomes key.
You can easily tell the smaller container
is much faster than the large container.
And you're probably running multiple containers
on your Kubernetes cluster, so these times can add up quickly.
Using small, common base images for your containers
significantly speeds up the deployment
times and speed at which new Kubernetes
nodes can come online.
Now let's look at security.
People often say that containers are
more secure if they're smaller, because they have less surface
area for attacks.
Let's see if this is actually true.
An awesome feature of Google Container Registry
is that it can automatically scan your containers
for vulnerabilities.
So I built both the onbuild and multistage containers
a few months ago.
Let's see if there's any vulnerabilities
in these old images.
Wow, there's only 3 medium vulnerabilities
for the small container, but 16 critical and over 370
for the larger container.
If we drill into the larger container,
we can see that most of the issues
have nothing to do with their app,
but rather programs that we're not even using.
When people talk about an increased surface
area for attacks, this is what they're referring to.
So remember-- build small containers.
The performance and security benefits are real.
I'll see you on the next episode of "Kubernetes Best Practices."
[MUSIC PLAYING]