Docker on Amazon Web Services
上QQ阅读APP看书,第一时间看更新

Testing and building the application using Docker Compose

In the previous section, you used Docker commands to perform the following tasks:

  • Build a test image
  • Run tests
  • Build a release image
  • Run the application

Each time we ran a Docker command, we had to supply quite a bit of configuration, and trying to remember the various commands that you need to run is already starting to become difficult. In addition to this, we also discovered that to start the release image for the application, we need to have an operational external database.  For local testing use cases, running an external database in another container is an excellent approach, but having to orchestrate this by running a series of Docker commands with lots of different input parameters very quickly becomes difficult to manage.

Docker Compose is a tool that allows you to orchestrate multi-container environments using a declarative approach, making it much easier to orchestrate complex workflows that may require multiple containers. By convention, Docker Compose looks for a file called docker-compose.yml in the current directory, so let's create this file at the root of the todobackend repository, alongside our Dockerfile:

version: '2.4'

services:
test:
build:
context: .
dockerfile: Dockerfile
target: test
release:
build:
context: .
dockerfile: Dockerfile
Docker Compose files are defined in a YAML format, which requires proper indentation to infer the correct relationships between parent, siblings and child objects or properties.  If you have not worked with YAML before, you can check out the  Ansible YAML Syntax guide , which provides a brief introduction to YAML formatting.  You can also use an online YAML linting tool  such as  http://www.yamllint.com/ to check your YAML, or install YAML support in your favourite text editor.

We first specify the version property, which is mandatory and references the version of the Compose file format syntax that we are using. If you are using Docker for local development and build tasks, I recommending using version 2.x of the Compose file format, as it includes some useful features, such as health checks on dependent services, that we will learn how to use shortly.  If you are using Docker Swarm to run your containers, then you should use version 3.x of the Compose file format, as this version supports a number of features that relate to managing and orchestrating Docker Swarm. 

If you choose to use version 3.x, your applications will need to be more robust in terms of dealing with scenarios such as your database not being available at application startup (see https://docs.docker.com/compose/startup-order/), which is a problem we will encounter later on in this chapter.  

We next specify the services property, which defines one or more services that run in our Docker Compose environment. In the preceding example, we create two services that correspond to the test and release stages of our workflow, and then add a single build property to each service, which defines how we want to build the Docker image for each service. Note that the build properties are based upon the various flags that we passed to the docker build command—for example, when we build the test stage image, we set the build context to the local folder, used the local Dockerfile as the build specification for the image, and targeted only the test stage for building the image. Rather than imperatively specifying these settings each time we run a Docker command, we are declaratively defining the desired configuration for the build process, which is an important distinction.

Of course we need to run a command to actually build these services, which you can do by running the docker-compose build command at the root of the todobackend repository:

> docker-compose build test
Building test
Step 1/12 : FROM alpine AS test
---> 3fd9065eaf02
Step 2/12 : LABEL application=todobackend
---> Using cache
---> 23e0c2657711
...
...
Step 12/12 : CMD ["python3", "manage.py", "test", "--noinput", "--settings=todobackend.settings_test"]
---> Running in 1ac9bded79bf
Removing intermediate container 1ac9bded79bf
---> f42d0d774c23

Successfully built f42d0d774c23
Successfully tagged todobackend_test:latest

You can see that running the docker-compose build test command achieves the equivalent of the earlier docker build command we ran, however, we don't need to pass any build options or configuration to the docker-compose command, given all of our specific settings are captured in the docker-compose.yml file.

If you now want to run tests from the newly built image, you can execute the docker-compose run command:

> docker-compose run test
Creating network "todobackend_default" with the default driver
nosetests --verbosity=2 --nologcapture --with-coverage --cover-package=todo --with-spec --spec-color --with-xunit --xunit-file=./unittests.xml --cover-xml --cover-xml-file=./coverage.xml
Creating test database for alias 'default'...

Ensure we can create a new todo item
- item has correct title
- item was created
- received 201 created status code
- received location header hyperlink
...
...
...
...
Ran 12 tests in 0.316s

OK

Destroying test database for alias 'default'...

You can also extend the Docker Compose file to add port mapping and command configurations to services, as demonstrated in the following example:

version: '2.4'

services:
test:
build:
context: .
dockerfile: Dockerfile
target: test
release:
build:
context: .
dockerfile: Dockerfile
ports:
- 8000:8000
command:
- uwsgi
- --http=0.0.0.0:8000
- --module=todobackend.wsgi
- --master

Here we specify that when the release service is run, it should create a static port mapping from port 8000 on the host to port 8000 on the container, and pass the uwsgi command we used earlier to the release container.  If you now run the release stage using the docker-compose up command, note that Docker Compose will automatically build the image for a service if it does not yet exist, and then start the service:

> docker-compose up release
Building release
Step 1/22 : FROM alpine AS test
---> 3fd9065eaf02
Step 2/22 : LABEL application=todobackend
---> Using cache
---> 23e0c2657711
...
...

Successfully built 5b20207e3e9c
Successfully tagged todobackend_release:latest
WARNING: Image for service release was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating todobackend_release_1 ... done
Attaching to todobackend_release_1
...
...
release_1 | *** uWSGI is running in multiple interpreter mode ***
release_1 | spawned uWSGI master process (pid: 1)
release_1 | spawned uWSGI worker 1 (pid: 6, cores: 1)
release_1 | spawned uWSGI http 1 (pid: 7)
You typically use the   docker-compose up  command for long-running services, and the   docker-compose run  command to run short-lived tasks. You also cannot override the command arguments passed to docker-compose up, whereas you can pass command overrides to the   docker-compose run  command.