Cost-effective resources
Container images can affect the overall cost of the system and workloads. In this section, we’ll describe what has to be done in order to optimize your container images, and why it is important from cost perspective.
CONTAINER_BUILD_COST_03: Ensure that your container images contain only what is relevant for your application to run |
---|
Container image size affects several processes related to running your containerized workload. The following processes can be affected by having unnecessary large container image size.
Containerized application start-up time
Container image size affects the time needed for an image to be pulled from a container registry. Large image sizes (hundreds, or thousands of MB), can lead to a slow startup time of the application, which can lead to:
-
Waste compute resources while waiting for images to be pulled.
-
Slow scale-out operations.
Container image size also affects the scaling time needed for a containerized application to become ready to receive traffic. This time can translate to a waste of resources. In small-scale replicas of your application, the waste might not be notable, but when dealing with a dynamic autoscaled environment, a 30-second delay between a triggered scale-out event and a container ready to run can result in hundreds of compute minutes wasted per month. To put this delay into an equation, 30 seconds multiplied by 100 pod launches per day, over a period of one month, can result in:
30(sec)*100(launches of pods)*30 (days)=90,000 seconds = 1,500 minutes of compute time that is wasted.
Storage requirements for containers
Consider your instance’s storage requirements depending on your container image size. The size of your container image has a direct effect on the instance storage size that the container will run on. This can result in the need for a larger storage size for your instances.
Container image size also affects the storage requirements of
the container registry, since the container image will be
stored in the registry. Stored images in Amazon ECR are priced
per GB-month. For current pricing, refer to the
pricing
page
CONTAINER_BUILD_COST_04: How do you reduce your container images size? |
---|
A container image consists of read-only layers that represent a Dockerfile
instruction. Each instruction creates one additional layer on top of the previous layer.
Running multiple consecutive commands can result in a large container image size, even if
we delete content in the container image itself. An example of that might be installing a
package, and deleting the cached downloaded files that are not needed anymore after
installing the package. The following example shows that we installed
some-package
and then delete the cached files. Even though we used the
rm
command to remove the cached file, the container image contains a layer
representing the rm -rf ...
command, and is still containing a layer with the
actual cached files, resulting in a larger container image.
RUN apt-get install -y some-package RUN rm -rf /var/lib/apt/lists/*
In order to overcome this, we can concatenate commands, and use a one-liner approach to install packages and remove the cached files in a single command:
RUN apt-get install -y some-package && rm -rf /var/lib/apt/lists/*
Reducing image layers can be done with several techniques:
-
Building container images from the scratch image - This can result in creating the minimal container image possible, especially when containerizing executable applications with minimal external dependencies from the OS (like Go, Rust, and C).
-
Use lightweight base images - For other type of programming languages that need a runtime environment in the container image, using parent and base images of lightweight distributions like Alpine can reduce the image size significantly. For example: a Python container image based on Debian parent image is ~330MB in size whereas a Python container image based on Alpine parent image is ~17MB in size.
-
Reducing the number of
RUN
instructions by chaining commands together - Installing dependencies and deleting cache in a single command as shown in the previous command. This practice should only be used when the consecutive commands relate one to another. -
Consider using package managers flags to reduce dependency sizes - Such as
no-install-recommends
withapt-get
. -
Use multi-stage builds - Multi-stage builds let you reduce image sizes by using build cache from the previous build step and copying only needed dependencies to the final container image. For example, see docker docs
. -
Follow Dockerfile best practices
such as: -
Use
COPY
instead ofADD
. -
Use absolute path when using
WORKDIR
. -
Exclude files from the build process using
.dockerignore
. Specify exclusion patterns of file, directories or both (similar to.gitignore
). This makes it easy to exclude unnecessary files fromCOPY
orADD
commands.
-
CONTAINER_BUILD_COST_05: How do you design your containerized application to support automatic scaling and graceful termination? |
---|
When designing applications that will be containerized, it is
important to include signal handling within the code and/or
the container itself. Handling signals is a fundamental
practice for writing applications, especially when writing
applications that will run inside a container. The application
should handle system signals and react according to the
application logic. Although this is not directly related to
cost, handling signals is a key element for using cost saving
practices like automatic scaling or using Amazon EC2 Spot
Instances. When a scale-in event, or replacement or
termination of a Spot Instance occurs, the container
orchestrator system or tools will send a SIGTERM
signal to the
application notifying the application to shut itself down
gracefully. If it fails to do so, the process may end up being
terminated while performing work, which can prohibit the use
of auto scaling or spot in general.
CONTAINER_BUILD_COST_06: How do you design your containerized application to support multiple CPU architectures? |
---|
Different instance families offer different performance for the same amount of
hardware (CPU and memory). An example is using a newer instead of an older generation of
instances, or using instances with different CPU architecture, such as ARM. To use a
different instance architecture, you have to change your build process. Since the default
behavior of the build process is to create a container image that is designed to run on
the architecture of the instance that it was built on, you have to create multiple images
for each CPU architecture. To create multiple images, run the same build process on an x86
instance, and on an ARM-based instance. Use tagging suffixes to differentiate between the
different architectures. You can see an example container image tag when searching images
in your registry (see aws-node-termination-handler

Figure 1. Example of container image manifests and manifest lists for multi-architecture container images