Skip to content

Latest commit

 

History

History
205 lines (138 loc) · 8.75 KB

08-docker.md

File metadata and controls

205 lines (138 loc) · 8.75 KB

Docker

In this lab, we will talk about managing containers for the first time in this tutorial. Particularly, we will talk about Docker which is the most widely used platform for running containers.

Intro

Remember when we talked about packer, we mentioned a few words about Immutable Infrastructure model? The idea was to package all application dependencies and application itself inside a machine image, so that we don't have to configure the system after start. Containers implement the same model, but they do it in a more efficient way.

Containers allow you to create self-contained isolated environments for running your applications.

They have some significant advantages over VMs in terms of implementing Immutable Infrastructure model:

  • Containers are much faster to start than VMs. Container starts in seconds, while a VM takes minutes. It's important when you're doing an update/rollback or scaling your service.
  • Containers enable better utilization of compute resources. Very often computer resources of a VM running an application are underutilized. Launching multiple instances of the same application on one VM has a lot of difficulties: different application versions may need different versions of dependent libraries, init scripts require special configuration. With containers, running multiple instances of the same application on the same machine is easy and doesn't require any system configuration.
  • Containers are more lightweight than VMs. Container images are much smaller than machine images, because they don't need a full operating system in order to run. In fact, a container image can include just a single binary and take just a few MBs of your disk space. This means that we need less space for storing the images and the process of distributing images goes faster.

Let's try to implement Immutable Infrastructure model with Docker containers, while paying special attention to the Dockerfile part as a way to practice Infrastructure as Code approach.

Install Docker Engine

The Docker Engine is the daemon that gets installed on the system and allows you to manage containers with simple CLI.

Install free Community Edition of Docker Engine on your system.

Verify that the version of Docker Engine is => 17.09.0:

$ docker -v

Create Dockerfile

You describe a container image that you want to create in a special file called Dockerfile.

Dockerfile contains instructions on how the image should be built. Here are some of the most common instructions that you can meet in a Dockerfile:

  • FROM is used to specify a base image for this build. It's similar to the builder configuration which we defined in a Packer template, but in this case instead of describing characteristics of a VM, we simply specify a name of a container image used for build. This should be the first instruction in the Dockerfile.
  • ADD and COPY are used to copy a file/directory to the container. See the difference between the two.
  • RUN is used to run a command inside the image. Mostly used for installing packages.
  • ENV sets an environment variable available within the container.
  • WORKDIR changes the working directory of the container to a specified path. It basically works like a cd command on Linux.
  • CMD sets a default command, which will be executed when a container starts. This should be a command to start your application.

Let's use these instructions to create a Docker container image for our raddit application.

Create a file called Dockerfile inside your iac-tutorial repo with the following content:

# Use base image with Ruby installed
FROM ruby:2.3

# install required system packages
RUN apt-get update -qq && \
    apt-get install -y build-essential

# create application directory and install dependencies
ENV APP_HOME /app
RUN mkdir $APP_HOME
WORKDIR $APP_HOME
COPY raddit-app/Gemfile* $APP_HOME/
RUN bundle install

# Copy the application code to the container
ADD raddit-app/ $APP_HOME
# Run "puma" command on container's start
CMD ["puma"]

This Dockerfile repeats the steps that we did multiple times by now to configure a running environment for our application and run it.

We first choose an image that already contains Ruby of required version:

# Use base image with Ruby installed
FROM ruby:2.3

The base image is downloaded from Docker official registry (storage of images) called Docker Hub.

We then install required system packages and application dependencies:

# install required system packages
RUN apt-get update -qq && \
    apt-get install -y build-essential

# create application home directory and install dependencies
ENV APP_HOME /app
RUN mkdir $APP_HOME
WORKDIR $APP_HOME
COPY raddit-app/Gemfile* $APP_HOME/
RUN bundle install

Then we copy the directory with application code and specify a default command that should be run when a container from this image starts:

# Copy the application code to the container
ADD raddit-app/ $APP_HOME
# Run "puma" command on container's start
CMD ["puma"]

Build Container Image

Once you defined how your image should be built, run the following command inside iac-tutorial directory to create a container image for raddit application:

$ docker build --tag raddit .

The resulting image will be named raddit. Find it in the list of your local images:

$ docker images | grep raddit

Bridge Network

We are going to run multiple containers in this setup. To allow containers communicate with each other by container names, we'll create a user-defined bridge network:

$ docker network create raddit-network

Verify that the network was created:

$ docker network ls

MongoDB Container

We shouldn't forget that we also need a MongoDB for our application to work.

The philosophy behind containers is that we create one container per process. So we'll run MongoDB in another container.

We will use a public image from Docker Hub to run a MongoDB container alongside raddit application container. However, I recommend you for the sake of practice write a Dockerfile for MongoDB and create your own image.

Because MongoDB is a stateful service, we'll first create a named volume for it to persist the data beyond the container removal.

$ docker volume create mongo-data

Check that volume was created:

$ docker volume ls | grep mongo-data

Now run the following command to download a MongodDB image and start a container from it:

$ docker run --name mongo-database \
    --volume mongo-data:/data/db \
    --network raddit-network \
    --detach mongo:3.2

Verify that the container is running:

$ docker container ls

Start Application Container

Start the application container from the image you've built:

$ docker run --name raddit-app \
    --env DATABASE_HOST=mongo-database \
    --network raddit-network \
    --publish 9292:9292 \
    --detach raddit

Note, how we also passed an environment variable with the command to the application container. Since MongoDB is not reachable at localhost as it was in the previous labs, we need to pass the environment variable with MongoDB address to tell our application where to connect. Automatic DNS resolution of container names within a user-defined network makes it possible to simply pass the name of a MongoDB container instead of an IP address.

Port mapping option (--publish) that we passed to the command is used to make the container reachable to the outsite world.

Access Application

The application should be accessible to your at http://localhost:9292

Save and commit the work

Save and commit the Dockerfile created in this lab into your iac-tutorial repo.

Conclusion

In this lab, you adopted containers for running your application. This is a different type of technology from what we used to deal with in the previous labs. Nevertheless, we use Infrastructure as Code approach here, too.

We describe the configuration of our container image in a Dockerfile using Dockerfile's syntax. We then save that Dockefile in our application repository. This way we can build the application image consistently across any environments.

Destroy the current playground before moving on to the next lab.

$ docker rm -f mongo-database
$ docker rm -f raddit-app
$ docker volume rm mongo-data
$ docker network rm raddit-network

Next: Docker Compose