In the last article we got our hands on the Docker Engine, pretty basic. Today we’re going to smuggle some Images & Containers and briefly talk about the Registry and Repositories.

High level picture of each Component

Docker Engine : The Shipping Yard

Docker Images : Shipping Manifests

Docker Containers : Shipping Containers

Before this streamlined flow of Containers, taking an app to production took embarrassing amounts of time.

You code an app in a Docker container at your workstation or your laptop and you run it unchanged wherever your production environment happens to be. Unbelievable fast time to production !

Security precautions should remain the same for unverified /untrusted code in production. Containers can contain malicious code.

Alright it’s time to start peeking through the list of components.

Engine in brief

This is our Docker program that we previously installed in the series. The shipping yard ! All the stuff that matters for application infrastructure and runtime dependencies. Things like root filesystems, network stacks, process hierarchies, variables, access to the kernel features, resource allocation, all the good things and all standardised. Every Docker install looks the same from one Docker host to another.

This standardisation brings AWESOME things, as long as the runtime environment provided by the container technology is the same then no matter what the underlined platform be that VMs on your workstation, bare metal on your datacenter, or instances in the cloud.. you can run your application with no changes necessary.

So this is the Docker Engine : A standardised runtime environment that looks and feels the same no matter what platform it’s running on, and it makes application portability insanely trivial.

docker run -it fedora /bin/bash

Docker Images are what we launch containers from. They are like templates in a virtual machine world. If you dont specify the image version docker will pull the :latest version from the Docker Hub cloud.

Images contain all the data and metadata needed for firing up a container.
They are locally stored in your /var/lib/docker/< storage-driver >/

If our images are the buildtime constructs then our containers are the runtime constructs. A Container is basically a running instance of an image.

Basics Out : Deep Dive

Docker Images are considered being layered (or stacked) … huummm … meaning a single Image might be formed by a group of stacked Images.

Why ? Modularity with super thin layers !

Each layer gets its own unique id, then these uuids are listed inside our Docker Image (purple) plus some metadata that tells Docker how to build the Container at runtime.

At runtime we have a single combined view of all layers. If there’s a conflict (updated version of the same file at an upper layer) the higher layer wins.

How ? Union Mounts.

The ability to mount multiple filesystems over the top of each other. All the layers on this image are mounted as read only (can be shared by many Containers) and the top layer (an additional layer that is added when we launch a Container) is the only writable layer. Meaning that when we edit a file from a read only layer in fact you are copying it to the Container R/W layer and saving the changes there.

When we start a container there is a small bootfs that exists below the rootfs and it’s very short-lived (not there after the container started). You don’t need to worry about this lightweight layer.

Image Layers

Look how it will download those multiple layers we just talked about when you pull an Image out of the Hub.

$ docker run -it ubuntu:14.04.1 /bin/bash
root@12d7aa42efb5:/#
# To exit without stopping the container hit Ctrl + P + Q
# We call this "detaching" from our Container
root@12d7aa42efb5:/# %
$
# But we can stop it from the outside with a SIGTERM
$ docker stop 12d7aa42efb5
# or a SIGKILL
$ docker kill 12d7aa42efb5
# or another signal with
$ docker kill -s <signal> 12d7aa42efb5

These signal go inside the container to the process running PID 1, in our case is the bash process. It’s important to know that to get a shell prompt after you attach the Container has to be running a shell as process 1. The attach command attaches us to the PID1 inside the container.

But but.. PID 1 should be init

For the sake of simplicity let’s say all the Linux server processes are forked from the init (the mother of all processes) and she is used to receive the signals and gracefully kill / stop / manage its child processes.

On the other hand our Containers usually run Application daemons that are not designed to do the init job, so in the event of receiving a SIGTERM there is no guarantee they will warn the other processes. This is one of the major reasons why we should run one process per Container.

We can and it’s a reality running multiple processes per Container, it’s your choice.

Containers and Micro-services

Microservices, is the new kid in the block, and is considered the next big thing in software architecture. You can look at it as ‘fine-grained SOA’ - an approach to developing an application as a suite of small services, each running in its own process and communicating with each other using some light-weight mechanism, with independent deployments, scalability and portability. You should already see how Containers just perfectly fits into that context!

Container brings in the isolated process space, with managed deployments and scaling. They are a very good fit for acting as deployment units for services. The orchestration capabilities, especially deploying, updating and scaling of containers independently is a perfect fit for microservices architecture.

One of the main complexities of a microservices architecture is in managing/operating a distributed set of services. To address this, a bunch of frameworks have evolved with the primary goal of simplifying the management of services/containers in a cluster/distributed environment. Google’s open source project Kubernetes is gaining a lot of traction. There is Apache Mesos, Fleet and few others as well. Kubernetes acts as an orchestration engine for containers, taking care of the scheduling and deployment onto nodes in a compute cluster and managing workload. Another important aspect of microservices that frameworks like Kubernetes addresses is service discovery and fault tolerance.