Docker-compose check if mysql connection is ready

后端 未结 9 2021
一个人的身影
一个人的身影 2020-11-27 13:40

I am trying to make sure that my app container does not run migrations / start until the db container is started and READY TO accept connections.

So I decided to use

相关标签:
9条回答
  • 2020-11-27 13:57

    If you are using docker-compose v3+, condition as an option of depends_on has been removed.

    The recommended path is to use rather wait-for-it, dockerize, or wait-for. In your docker-compose.yml file, change your command to be:

    command: sh -c 'bin/wait-for db:3306 -- bundle exec rails s'
    

    I personally prefer wait-for since it can run in an Alpine container (sh compatible, no dependance on bash). Drawback is that it depends on netcat, so if you decide to use it, make sure you have netcat installed in the container, or install it in your Dockerfile, for example with:

    RUN apt-get -q update && apt-get -qy install netcat
    

    I also forked the wait-for project so it can check for healthy HTTP status (it uses wget). Then you can do something like that:

    command: sh -c 'bin/wait-for http://api/ping -- jest test'
    

    PS: A PR is also ready to be merged to add that capacity to wait-for project.

    0 讨论(0)
  • 2020-11-27 13:57

    Adding an updated solution for the healthcheck approach. Simple snippet:

    healthcheck:
      test: out=$$(mysqladmin ping -h localhost -P 3306 -u foo --password=bar 2>&1); echo $$out | grep 'mysqld is alive' || { echo $$out; exit 1; }
    

    Explanation: Since mysqladmin ping returns false positives (especially for wrong password), I'm saving the output to a temporary variable, then using grep to find the expected output (mysqld is alive). If found it will return the 0 error code. In case it's not found, I'm printing the whole message, and returning the 1 error code.

    Extended snippet:

    version: "3.8"
    services:
      db:
        image: linuxserver/mariadb
        environment:
          - FILE__MYSQL_ROOT_PASSWORD=/run/secrets/mysql_root_password
          - FILE__MYSQL_PASSWORD=/run/secrets/mysql_password
        secrets:
          - mysql_root_password
          - mysql_password
        healthcheck:
          test: out=$$(mysqladmin ping -h localhost -P 3306 -u root --password=$$(cat $${FILE__MYSQL_ROOT_PASSWORD}) 2>&1); echo $$out | grep 'mysqld is alive' || { echo $$out; exit 1; }
    
    secrets:
      mysql_root_password:
        file: ${SECRETSDIR}/mysql_root_password
      mysql_password:
        file: ${SECRETSDIR}/mysql_password
    

    Explanation: I'm using docker secrets instead of env variables (but this can be achieved with regular env vars as well). The use of $$ is for literal $ sign which is stripped when passed to the container.

    Output from docker inspect --format "{{json .State.Health }}" db | jq on various occasions:

    Everything alright:

    {
      "Status": "healthy",
      "FailingStreak": 0,
      "Log": [
        {
        {
          "Start": "2020-07-20T01:03:02.326287492+03:00",
          "End": "2020-07-20T01:03:02.915911035+03:00",
          "ExitCode": 0,
          "Output": "mysqld is alive\n"
        }
      ]
    }
    

    DB is not up (yet):

    {
      "Status": "starting",
      "FailingStreak": 1,
      "Log": [
        {
          "Start": "2020-07-20T01:02:58.816483336+03:00",
          "End": "2020-07-20T01:02:59.401765146+03:00",
          "ExitCode": 1,
          "Output": "\u0007mysqladmin: connect to server at 'localhost' failed error: 'Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2 \"No such file or directory\")' Check that mysqld is running and that the socket: '/var/run/mysqld/mysqld.sock' exists!\n"
        }
      ]
    }
    

    Wrong password:

    {
      "Status": "unhealthy",
      "FailingStreak": 13,
      "Log": [
        {
          "Start": "2020-07-20T00:56:34.303714097+03:00",
          "End": "2020-07-20T00:56:34.845972979+03:00",
          "ExitCode": 1,
          "Output": "\u0007mysqladmin: connect to server at 'localhost' failed error: 'Access denied for user 'root'@'localhost' (using password: YES)'\n"
        }
      ]
    }
    
    0 讨论(0)
  • 2020-11-27 14:00

    Hi for a simple healthcheck using docker-compose v2.1, I used:

    /usr/bin/mysql --user=root --password=rootpasswd --execute \"SHOW DATABASES;\"
    

    Basically it runs a simple mysql command SHOW DATABASES; using as an example the user root with the password rootpasswd in the database.

    If the command succeed the db is up and ready so the healthcheck path. You can use interval so it tests at interval.

    Removing the other field for visibility, here is what it would look like in your docker-compose.yaml.

    version: '2.1'
    
      services:
        db:
          ... # Other db configuration (image, port, volumes, ...)
          healthcheck:
            test: "/usr/bin/mysql --user=root --password=rootpasswd --execute \"SHOW DATABASES;\""
            interval: 2s
            timeout: 20s
            retries: 10
    
         app:
           ... # Other app configuration
           depends_on:
             db:
             condition: service_healthy
    
    0 讨论(0)
  • 2020-11-27 14:01

    I had the same problem, I created an external bash script for this purpose (It is inspired by Maxim answer). Replace mysql-container-name by the name of your MySQL container and also password/user is needed:

    bin/wait-for-mysql.sh:

    #!/bin/sh
    until docker container exec -it mysql-container-name mysqladmin ping -P 3306 -proot | grep "mysqld is alive" ; do
      >&2 echo "MySQL is unavailable - waiting for it...                                                                     
    0 讨论(0)
  • 2020-11-27 14:05

    This should be enough

    version: '2.1'
    services:
      mysql:
        image: mysql
        ports: ['3306:3306']
        environment:
          MYSQL_USER: myuser
          MYSQL_PASSWORD: mypassword
        healthcheck:
          test: mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD
    
    0 讨论(0)
  • 2020-11-27 14:09

    If you can change the container to wait for mysql to be ready do it.

    If you don't have the control of the container that you want to connect the database to, you can try to wait for the specific port.

    For that purpose, I'm using a small script to wait for a specific port exposed by another container.

    In this example, myserver will wait for port 3306 of mydb container to be reachable.

    # Your database
    mydb:
      image: mysql
      ports:
        - "3306:3306"
      volumes:
        - yourDataDir:/var/lib/mysql
    
    # Your server
    myserver:
      image: myserver
      ports:
        - "....:...."
      entrypoint: ./wait-for-it.sh mydb:3306 -- ./yourEntryPoint.sh
    

    You can find the script wait-for-it documentation here

    0 讨论(0)
提交回复
热议问题