×
Community Blog Practical Exercises for Docker Compose: Part 1

Practical Exercises for Docker Compose: Part 1

This set of tutorials focuses on giving you practical experience on using Docker Compose when working with containers on Alibaba Cloud.

By Alwyn Botha, Alibaba Cloud Tech Share Author. Tech Share is Alibaba Cloud's incentive program to encourage the sharing of technical knowledge and best practices within the cloud community.

This set of tutorials focuses on giving you practical experience on using Docker Compose when working with containers on Alibaba Cloud Elastic Compute Service (ECS). This tutorial focuses on you using the shell a lot: interactively exciting.

After you completed this set of 5 tutorials you can spend time reading the official Docker Compose documentation: https://docs.docker.com/compose/compose-file/. Everything will make a lot more sense since you now have actual practical (simple) experience with most of the Docker Compose instructions.

Part 1 of 5 demonstrates several docker-compose configuration options that can be explored in isolation. Every separate heading section below explores just ONE docker-compose option - making the config files very short and very easy to understand.

Let's get started.

Prerequisites

You need access to a server/VPS with a recent version of Docker already installed. In this tutorial, I will be using an Alibaba Cloud Elastic Compute Service (ECS) instance with CentOS and Docker.

You don't need to purchase a large instance to follow this tutorial. Even when swarms of 6 containers are running in later tutorials you still need a server with only a total RAM of 512MB.

Although I am writing this tutorial using CentOS, you can also use Debian or Ubuntu. A large majority of this tutorial will work on any Linux distributions since it mostly uses Docker Compose commands.

You need a very basic understanding of Docker, images, containers and using docker-compose and docker ps -a. You need to have used docker-compose up around 3 times to roughly understand its concepts.

The purpose of this tutorial is to get you to docker-compose up several very simple docker compose files. Your understanding of Docker compose concepts will grow as you use it.

Clean Up Preparation

It will really help if you have only a few containers (preferably none) running. That way you can easily find your tutorial container in docker ps -a output lists.

So stop containers and removes containers created by docker-compose up:

(Skip this step if you don't have any)

docker-compose down -t 0 --remove-orphans 

So stop and prune all the containers you do not need running.

You can quickly do that (in your DEVELOPMENT environment) using:

docker stop $(docker ps -a -q) #stop ALL containers

To now remove all containers, run

docker rm -f $(docker ps -a -q) # remove ALL containers

Separate Directory and Editor

Its better to have your docker-compose files in separate directories. One directory per docker-compose service / stack or swarm: neat, organized, small, efficient, understandable in its entirely due to all these attributes.

Therefore, enter:

mkdir compose-tuts
cd compose-tuts

There are two editors frequently used in Linux distros: vim and nano. I prefer nano, but vim should work fine as well.

To get nano installed:

Debian / Ubuntu :

apt install nano

CentOS :

yum install nano

Nano beginners guide:

* nano filename-to-edit ... this starts the nano editor
* cursor keys move around as expected
* cut tutorials text from alibaba tutorials web pages work as expected
* pasting into your docker-compose file work as expected
* press F3 to save your work
* press F2 to exit the editor.

Those are the only things you need to know to use nano for all 5 of these docker-compose tutorials.

image

This works identical to image in Dockerfiles. Image specifies the image to build the container from.

Every exercise below uses it.

tmpfs

Reference : https://docs.docker.com/compose/compose-file/#tmpfs

tmpfs mounts a (in RAM) temporary file system inside the container. It can be a single value or a list. Let's do a demo with a list of 2 temp file systems:

Add the following to your docker-compose.yml using

nano docker-compose.yml
version: "3.7"
services:
  alpine:

    image: alpine:3.8

    command: sleep 600

    tmpfs:

     - /my-run:size=10M

     - /my-app-tmp:size=20M

docker-compose up brings up the services defined within your docker-compose.yml file.
docker exec enters a running container - very similar to ssh into servers.

Run :

docker-compose up -d -t 0      
docker exec -it compose-tuts_alpine_1 /bin/sh

You should see the size of our 2 temp file systems:

/ # df -h /my*
Filesystem                Size      Used Available Use% Mounted on
tmpfs                    20.0M         0     20.0M   0% /my-app-tmp
tmpfs                    10.0M         0     10.0M   0% /my-run

Perfect. Mounted as requested, sizes as requested. Zero percent used.

Let's write 15M of zeros to /my-run (10M in size)

/ # dd if=/dev/zero of=/my-run/output.dat  bs=1M  count=15
dd: writing '/my-run/output.dat': No space left on device
11+0 records in
10+0 records out
/ # echo $?
1

The dd command does not show an error, but $? gives the exit status of the last command: in this case a 1 : meaning error.

Run df again:

/ # df -h /my*
Filesystem                Size      Used Available Use% Mounted on
tmpfs                    20.0M         0     20.0M   0% /my-app-tmp
tmpfs                    10.0M     10.0M         0 100% /my-run

/my-run fully used as expected. docker-compose successfully created our tmpfs the correct size and it works as expected.

Let's write 15M of zeros to /my-app-tmp (20M in size)

/ # dd if=/dev/zero of=/my-app-tmp/output.dat  bs=1M  count=15
15+0 records in
15+0 records out
/ # echo $?
0

echo $? shows zero = success. Lets check /my-app-tmp used size:

/ # df -h /my*
Filesystem                Size      Used Available Use% Mounted on
tmpfs                    20.0M     15.0M      5.0M  75% /my-app-tmp
tmpfs                    10.0M     10.0M         0 100% /my-run

15M used. Perfect.

You now know how to define tmpfs to the correct sizes needed for the applications running in your containers.

You can use man dd to learn more about the dd command.

Spoiler: if = input file, of = output file, bs = block size. Simple as that.

env_file and environment

env_file and environment adds environment variables to your containers.

Environment variables declared in the environment section override env_file values.

Nearly all Linux applications have environment variables, so you need to know how to inject it into your containers. (The only application I know that does not need this is a hello word application)

To see how this works, we need some prep work:

  1. create environment files
  2. refer to those files via docker-compose
  3. then enter / exec container to see if environment variables is defined as expected.

Add the following environment variables to these files in your current directory :

myvars.env

ENV_VAR_A=1
ENV_VAR_B=10
ENV_VAR_C=apple
ENV_VAR_F=CentOS 

mynewvars.env

ENV_VAR_A=2
ENV_VAR_C=pear
ENV_VAR_D=Docker 

For the 3rd file, to save you some typing, just cut and paste the text below to your shell prompt:

cat <<EOT >> mybestvars.env
ENV_VAR_A=3
ENV_VAR_B=333
ENV_VAR_C=lemon
ENV_VAR_E=Kubernetes 
EOT

(The cat sends all the ENV lines to mybestvars.env)

You can learn more about it here: https://www.tldp.org/LDP/abs/html/here-docs.html

Can you already see how similarly named variables will be overridden?

Add the following to your docker-compose.yml using

nano docker-compose.yml
version: "3.7"
services:
  alpine:

    image: alpine:3.8

    command: sleep 600

    env_file:
      - myvars.env
      - mynewvars.env
      - mybestvars.env

    environment:
      - ENV_VAR_A=42
      - ENV_VAR_E=Apache web server

Run:

docker-compose up -d -t 0 
docker exec -it compose-tuts_alpine_1 /bin/sh

Execute printenv as shown:

/ # printenv
HOSTNAME=1c8abf6141ec
SHLVL=1
HOME=/root
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ENV_VAR_A=42
ENV_VAR_B=333
ENV_VAR_C=lemon
PWD=/
ENV_VAR_D=Docker
ENV_VAR_E=Apache web server
ENV_VAR_F=CentOS
/ # exit

How were those values determined?

ENV_VAR_A=42 ... the value from environment: in the docker-compose file.
environment: value overrides all the values set in all 3 those files.

ENV_VAR_B=333 ... the value from the last file: mybestvars.env
ENV_VAR_C=lemon ... the value from the last file: mybestvars.env
ENV_VAR_D=Docker ... value from mynewvars.env: never overridden anywhere
ENV_VAR_E=Apache web server... the value from environment: in the docker-compose file.
ENV_VAR_F=CentOS ... value from first file: myvars.env : never overridden anywhere

That is how environment variables work. Later files override similar named variables in previous files. BUT environment: in the docker-compose file overrides ALL.

Those 3 files do not exist in the container, but their content lives on in those environment variables.

Important: the 3 environment files you created in your HOST shell is important. They are part of your docker-compose file and should be kept in source control, along with your docker-compose file.

extra_hosts

extra_hosts links hostnames to their ip addresses in the /etc/hosts file inside the container.

Add the following to your docker-compose.yml using

nano docker-compose.yml
version: "3.7"
services:
  alpine:

    image: alpine:3.8

    command: sleep 600

    extra_hosts:
      - "myhost:123.242.195.42"
      - "myotherhost:150.31.209.42"

Run:

docker-compose up -d -t 0 

docker exec -it compose-tuts_alpine_1 /bin/sh

Enter commands shown: ip r : shows routing table and cat /etc/hosts shows hosts file contents.

Docker adds nothing to routing tables to enable the container to find those 2 new hosts. ALL it does is add those 2 mappings to the /etc/hosts file.

/ # ip r
default via 172.22.0.1 dev eth0
172.22.0.0/16 dev eth0 scope link  src 172.22.0.2
/ # cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
123.242.195.42  myhost
150.31.209.42   myotherhost
172.22.0.2      024481ed6ce7
/ # exit

Those hostname mappings can only contain fixed ip addresses.

That last random looking hostname is the hostname of this container. If you do a hostname command at the CONTAINER prompt you will see that as the hostname.

healthcheck

If you are already familiar with Dockerfile health checks, docker-compose health checks are easy: its just some minor syntax differences.

Add the following to your docker-compose.yml using

nano docker-compose.yml
version: "3.7"
services:
  alpine:

    image: alpine:3.8

    command: sleep 600

    healthcheck:
      test: exit 0
      interval: 1s
      timeout: 1s
      retries: 3
      start_period: 0s

Run:

docker-compose up -d -t 0

docker ps -a

If you have a speedy computer you will see this: health checks ... still starting.

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                           PORTS               NAMES
2979837d1a56        alpine:3.8          "sleep 600"         3 seconds ago       Up 1 second (health: starting) 

On a slower computer / server you will have to run the docker ps -a command multiple times before you can complete the health checks successfully, showing: (healthy)

docker ps -a

Output:

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                   PORTS               NAMES
d7ceefcc2ad2        alpine:3.8          "sleep 600"         6 seconds ago       Up 4 seconds (healthy)                       compose-tuts_alpine_1

Edit your docker-compose.yml - modify around line 10 to look like:

      test: exit 1

Now the health check will fail completely - with error exit code 1.

Rerun:

docker-compose up -d -t 0 
docker ps -a
docker ps -a
docker ps -a

Within seconds you will see: (unhealthy)

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
ed54a3189e7a        alpine:3.8          "sleep 600"         6 seconds ago       Up 5 seconds (unhealthy)                       compose-tuts_alpine_1

Depending on your application, you have to determine which exact health checks are appropriate for each specific application.

You also have to determine an appropriate interval: and timeout value.

One second for both of those is too low for production. 1 second is used here for quick testing and demo purposes only.

Similarly start_period cannot be zero in real life. You should give your application time to start up first, BEFORE you start running health checks.

You can leaern more about health checks on Alibaba Cloud on their official documentation: Container Service Best Practices - Health Check of Docker Containers

The Docker community provides some instance images that contain health checks at https://github.com/docker-library/healthcheck

Even if you do not use the software in those examples you can still get very good ideas of how to write your own health checks.

0 1 1
Share on

Alibaba Clouder

2,599 posts | 764 followers

You may also like

Comments