If you're working in Tech or follow the industry, you have probably heard about containers, but you may not know what they are. We're going to take a brief look at what a container is and how they can be useful, along with explaining some of the terminology used.
It's worth noting there are many tools available to allow you to build and run containers. In this article, we will focus on Docker due to its prominence.
Containers allow you to package application code, dependencies, and configurations into a single object that can be deployed in any environment. Containers are created from container images and are only considered a container when they are running. There is an Open Container Initiative which aims to set industry standards regarding container formats.
Benefits of Containers
A container isolates its software from its environment, ensuring that each deployed container behaves identically from machine to machine. Each container’s image includes an application and all of it’s required libraries, binaries and configuration files. This comprehensive inclusion of an application’s runtime means you can deploy the same container to Edge Compute, your laptop, or any other machine consistently and with confidence.
It's also possible to set up a container registry or use a public registry like Docker Hub, allowing you to push your containers to a central location for storage. This also enables quick and easy deployments by simply configuring your registry on your hosts. Once your registry is configured you'll be able to pull your images from the registry.
Container images also make use of layers, which allow you to share a common base between containers, saving changes to a new layer which then becomes a part of the new image. This allows you to be able to create or extend container images quickly as you'll often already have the layers you need storing locally, especially if your base images are the same.
Unlike virtual machines, containers do not virtualize the hardware. Instead, they virtualize at the Operating System level and share the OS kernel. This allows containers to be extremely lightweight in terms of size and CPU and Memory resources used and allows for extremely quick startup speed.
You're able to tag your container images, allowing you to create multiple versions of the same image with minor differences. This is incredibly useful and powerful when paired with a Continuous Integration/Continuous Deployment pipeline as it allows you to automatically generate new, discrete versions of your application, automatically tagged with something unique like a commit hash or the application version number, or a combination of both. Typically if a tagged container image is pushed to a registry where the container image and tag already exist the previous version is overwritten by the new version.
Container Images Creation
Typically container images are created from a Dockerfile which is what we'll focus on in this article to keep things simple. Dockerfiles are text files which contain commands, in order, specifying how the image should be built.
Each line of a Dockerfile contains an instruction which creates a new read-only layer of the image, built from the previous layer of the image, or in the case of a FROM instruction, an image specified in the Dockerfile.
Each line of the Dockerfile corresponds to a layer of the image which is created when the Dockerfile is built. This is helpful as it allows us to build from other images, extending their functionality. Handily, Docker provides a library of official images which are regularly updated and are very useful to build from.
Dockerfile is the most common way to create a container image, so we will take a look at a basic example to see how they work. Let's create an image that runs a python application, my_app. Our example Dockerfile is below, which will be added to a file called
FROM ubuntu:18.04 RUN apt-get update && apt-get install python-pip -y COPY requirements.txt /my_app/requirements.txt RUN pip install -r /my_app/requirements.txt COPY . /my_app CMD python /my_app/app.py
Let's break this down line by line to find out what's going to happen when we execute a build:
The first layer is created from the Ubuntu library image from Docker Hub. The string after the colon indicates the tag, in this instance we're using the version tagged with 18.04. This means we will get a base image derived from Ubuntu 18.04.
RUN apt-get update && apt-get install python-pip -y
apt-get updateis run and
python-pipis installed. The
-yflag is used here to tell apt-get to accept with yes without input from the user as we cannot interact with the executed commands during a container build. This creates another layer on top of the base image layer with these changes, which in this instance will include the python-pip software from the Ubuntu repository and any of its dependencies.
COPY requirements.txt /my_app/requirements.txt
Copy the file
/my_app/requirements.txtin the container. Input files are relative to the build context, where the build context is the directory in which the Docker build is occurring. Another layer is created which contains the requirements file.
- RUN pip install -r /my_app/requirements.txt
pip(a python package manager) is executed in the container, installing the dependencies listed in
/my_app/requirements.txt. A new layer is created which contains the installed dependencies.
- COPY . /my_app
The contents of the build context directory are copied to the container at
/my_app. In this example, this will include our application code, which is added to the new layer created by this instruction.
- CMD python /my_app/app.py
The command for the container is set. Here we're executing
/my_app/app.pywith python. When the container is started this command will run until it exits, if the command exits (for example due to an error) the container will stop too.
You may have noticed that we copied the requirements.txt file to the container before we copied our application code. We're taking advantage of image layers here to keep our image builds as quickly as possible. We know that typically our python dependencies will be quite static, so we copy the requirements.txt file to the container and then install the python dependencies early on in the Dockerfile. This means that if we add a new feature to our application, only that layer needs to change and the previous layers do not need to be recreated. Assuming no new dependencies were added we can skip recreating the first 4 layers as they have already been created.
More commands are available, you can refer to the Dockerfile reference for more information.
Orchestration and Complex Deployments
One of the great benefits of containerization is the isolation of containers, however, as containers are designed to run one command it is beneficial to launch containers together, utilizing a microservice approach for your application. Tools such as kubernetes and docker-compose can be used to automatically manage your containers, ensuring they are always up, running at scale and also enabling inter-container networking. Kubernetes takes orchestration one step further allowing you to easily schedule recurring or one-time jobs. Docker has comprehensive documentation for docker-compose on its website, along with tutorials. Kubernetes is also extensively documented.
StackPath's Edge Compute platform is currently in its infancy, however, we are constantly working to add functionality and new features, including advanced features such as cluster DNS which enables inter-container DNS lookups.