docker compose service startup sequence control method

  • 2020-12-10 00:55:55
  • OfStack

The profile

docker-compose makes it easy to combine multiple docker container services, but docker-ES7en does not guarantee the starting order of the services when there are dependencies between the container services.

The depends_on configuration in ES10en-ES11en is the order in which the container is started, not the order in which the services in the container are started.

Problem reproduction

First, let's construct an example to illustrate the problems caused by ES18en-ES19en. The docker-ES21en. yml file is as follows:


version: '2'
services:
 web:
  image: ubuntu:14.04
  depends_on:
   - web
  command: nc -z database 3306

 database:
  image: ubuntu:14.04
  command: >
   /bin/bash -c '
   sleep 5;
   echo "sleep over";
   nc -lk 0.0.0.0 3306;
   '

After startup, you can see that database was indeed started first and web later, but the service in database took about 5 seconds to complete, causing the startup of web to fail.


$ docker-compose up
Creating tmp_database_1 ... done
Creating tmp_database_1 ...
Creating tmp_web_1   ... done
Attaching to tmp_database_1, tmp_web_1
tmp_web_1 exited with code 1
database_1 | sleep over

Problem resolution 1.0

Modify the startup script for web and wait for database's port to pass before starting the service


version: '2'
services:
 web:
  image: ubuntu:14.04
  depends_on:
   - database
  command: >
   /bin/bash -c '
   while ! nc -z database 3306;
   do
    echo "wait for database";
    sleep 1;
   done;

   echo "database is ready!";
   echo "start web service here";
   '

 database:
  image: ubuntu:14.04
  command: >
   /bin/bash -c '
   sleep 5;
   echo "sleep over";
   nc -lk 0.0.0.0 3306;
   '

Start it up again,


$ docker-compose up
Creating tmp_database_1 ... done
Creating tmp_database_1 ...
Creating tmp_web_1   ... done
Attaching to tmp_database_1, tmp_web_1
web_1    | wait for database
web_1    | wait for database
web_1    | wait for database
web_1    | wait for database
web_1    | wait for database
database_1 | sleep over
web_1    | database is ready!
web_1    | start web service here
tmp_web_1 exited with code 0

web will not start until database is started and the port is connected.

Problem solving 2.0

The above solution solves the problem, but inserting scripts directly into yaml is not maintainable and error-prone. If you have multiple dependencies, or layers of dependencies, the complexity goes up linearly.

So, you want to encapsulate an ES56en.sh script that accepts the start command and the service and port to wait for. The script reads as follows:


#!/bin/bash
#set -x
#******************************************************************************
# @file  : entrypoint.sh
# @author : wangyubin
# @date  : 2018-08- 1 10:18:43
#
# @brief  : entry point for manage service start order
# history : init
#******************************************************************************

: ${SLEEP_SECOND:=2}

wait_for() {
  echo Waiting for $1 to listen on $2...
  while ! nc -z $1 $2; do echo waiting...; sleep $SLEEP_SECOND; done
}

declare DEPENDS
declare CMD

while getopts "d:c:" arg
do
  case $arg in
    d)
      DEPENDS=$OPTARG
      ;;
    c)
      CMD=$OPTARG
      ;;
    ?)
      echo "unkonw argument"
      exit 1
      ;;
  esac
done

for var in ${DEPENDS//,/ }
do
  host=${var%:*}
  port=${var#*:}
  wait_for $host $port
done

eval $CMD

This script takes two arguments, -ES62en needs to wait for the service and port, -ES63en waits for the service and port to start after its own startup command

Modify ES66en-ES67en.yml to use the ES69en.ES70en script to control the boot sequence.


version: '2'
services:
 web:
  image: ubuntu:14.04
  depends_on:
   - database
  volumes:
   - "./entrypoint.sh:/entrypoint.sh"
  entrypoint: /entrypoint.sh -d database:3306 -c 'echo "start web service here"';

 database:
  image: ubuntu:14.04
  command: >
   /bin/bash -c '
   sleep 5;
   echo "sleep over";
   nc -lk 0.0.0.0 3306;
   '

In practice, it is also possible to package entrypoint.sh into a published image instead of loading the entrypoint.sh script through the volumes configuration.

The test results are as follows:


$ docker-compose up
Starting tmp_database_1 ... done
Starting tmp_web_1 ... done
Attaching to tmp_database_1, tmp_web_1
web_1    | Waiting for database to listen on 3306...
web_1    | waiting...
web_1    | waiting...
web_1    | waiting...
database_1 | sleep over
web_1    | start web service here
tmp_web_1 exited with code 0

supplement

Depend on multiple services and ports

Using the entrypoint. sh script above, you can also rely on multiple services and ports, separated by commas (,) after the -d parameter.


version: '2'
services:
 web:
  image: ubuntu:14.04
  depends_on:
   - mysql
   - postgresql
  volumes:
   - "./entrypoint.sh:/entrypoint.sh"
  entrypoint: /entrypoint.sh -d mysql:3306,postgresql:5432 -c 'echo "start web service here"';

 mysql:
  image: ubuntu:14.04
  command: >
   /bin/bash -c '
   sleep 4;
   echo "sleep over";
   nc -lk 0.0.0.0 3306;
   '
 postgresql:
  image: ubuntu:14.04
  command: >
   /bin/bash -c '
   sleep 8;
   echo "sleep over";
   nc -lk 0.0.0.0 5432;
   '

The effect of the execution can be tried by yourself.

Try the interval configuration

The wait time per connection attempt can be configured using the environment variable SLEEP_SECOND. By default, the configuration wait time under 2 seconds is set to 4 seconds, so the connection can be made every 4 seconds when the mysql service is tried.


version: '2'
services:
 web:
  image: ubuntu:14.04
  environment:
   SLEEP_SECOND: 4
  depends_on:
   - mysql
  volumes:
   - "./entrypoint.sh:/entrypoint.sh"
  entrypoint: /entrypoint.sh -d mysql:3306 'echo "start web service here"';

 mysql:
  image: ubuntu:14.04
  command: >
   /bin/bash -c '
   sleep 4;
   echo "sleep over";
   nc -lk 0.0.0.0 3306;
   '

Related articles: