Docker-compose sprawdza, czy połączenie mysql jest gotowe

91

Próbuję się upewnić, że mój kontener aplikacji nie uruchamia migracji / uruchamiania, dopóki kontener bazy danych nie zostanie uruchomiony i GOTOWY DO akceptowania połączeń.

Postanowiłem więc skorzystać z funkcji Healthcheck i zależy od opcji w pliku redagowania docker v2.

W aplikacji mam następujące elementy

app:
    ...
    depends_on:
      db:
      condition: service_healthy

Z drugiej strony baza danych ma następującą kontrolę stanu

db:
  ...
  healthcheck:
    test: TEST_GOES_HERE
    timeout: 20s
    retries: 10

Wypróbowałem kilka podejść, takich jak:

  1. upewniając się, że db DIR został utworzony test: ["CMD", "test -f var/lib/mysql/db"]
  2. Pobieranie wersji mysql: test: ["CMD", "echo 'SELECT version();'| mysql"]
  3. Pinguj administratora (oznacza kontener db jako zdrowy, ale nie wygląda na prawidłowy test) test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]

Czy ktoś ma na to rozwiązanie?

John Kariuki
źródło
Utworzyłeś docker dla bazy danych? Proszę, powiedz mi, że Twoje dane znajdują się poza tym kontenerem ze względu na stan Twojej aplikacji
Jorge Campos,
A przynajmniej jest to pojemnik testowy.
Jorge Campos
To jest tylko do celów programistycznych / testowych.
John Kariuki
2
Myślę, że powinieneś użyć polecenia, aby połączyć się i uruchomić zapytanie w mysql, żadna z podanych próbek nie robi tego: coś takiego:mysql -u USER -p PASSWORD -h MYSQLSERVERNAME -e 'select * from foo...' database-name
Jorge Campos
1
@JorgeCampos Dobra, dzięki. Zwykle mam kontener db, ale woluminy mapowania dla katalogu danych. Tak więc, gdyby kontener przestał działać, dane przetrwałyby do następnej instancji.
S ..

Odpowiedzi:

80
version: "2.1"
services:
    api:
        build: .
        container_name: api
        ports:
            - "8080:8080"
        depends_on:
            db:
                condition: service_healthy
    db:
        container_name: db
        image: mysql
        ports:
            - "3306"
        environment:
            MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
            MYSQL_USER: "user"
            MYSQL_PASSWORD: "password"
            MYSQL_DATABASE: "database"
        healthcheck:
            test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
            timeout: 20s
            retries: 10

Kontener API nie uruchomi się, dopóki kontener db nie będzie zdrowy (w zasadzie dopóki mysqladmin nie uruchomi się i nie zaakceptuje połączeń).

John Kariuki
źródło
12
mysqladmin pingzwróci fałszywie dodatni wynik, jeśli serwer działa, ale jeszcze nie akceptuje połączeń.
halfpastfour.am
53
Do Twojej wiadomości dla ludzi z 2017 roku: conditionunder depends_onnie jest obsługiwany w wersji 3+
Mint
@BobKruithof Mam ten sam problem ... czy jest jakieś obejście, coś takiego jak stan uśpienia lub wyjścia dla ponownej próby
Mukesh Agarwal
1
@dKen zobacz moją odpowiedź poniżej stackoverflow.com/a/45058879/279272 , mam nadzieję, że to zadziała również dla Ciebie.
Mukesh Agarwal
1
Aby to sprawdzić za pomocą hasła: test: ["CMD", 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', '-p$$MYSQL_ROOT_PASSWORD' ]- jeśli określono MYSQL_ROOT_PASSWORDw environmentspkt.
laimison
22

Jeśli używasz docker-compose v3 + , conditionjako opcja depends_onzostała usunięta .

Zalecana ścieżka jest użycie raczej wait-for-it, dockerizealbo wait-for. W docker-compose.ymlpliku zmień polecenie na:

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

Osobiście wolę, wait-forponieważ może działać w kontenerze Alpine ( shkompatybilny, bez zależności bash). Wadą jest to, że to zależy netcat, więc jeśli zdecydujesz się go użyć, upewnij się, że masz netcatzainstalowany w kontenerze lub zainstaluj go w swoim Dockerfile, na przykład za pomocą:

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

Rozdzieliłemwait-for również projekt, aby mógł sprawdzić stan HTTP (używa wget). Następnie możesz zrobić coś takiego:

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

PS: PR jest również gotowy do połączenia, aby dodać tę zdolność do wait-forprojektu.

Capripot
źródło
13

To powinno wystarczyć

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
Maksim Kostromin
źródło
2
po co to podwójne $?
InsOp
5
Specjalna składnia @InsOp, której musisz użyć w poleceniu testu kondycji do ucieczki zmiennych env zaczyna się od $, tj. $$ MYSQL_PASSWORD da w wyniku $ MYSQL_PASSWORD, co samo w sobie spowoduje mojehasło w tym konkretnym przykładzie
Maksim Kostromin,
Więc mam dostęp do zmiennej env wewnątrz kontenera? z jednym $Im uzyskując dostęp do zmiennej env z hosta, a następnie przypuszczam? to miło, dziękuję!
InsOp
10

Jeśli możesz zmienić kontener, aby czekać, aż mysql będzie gotowy, zrób to.

Jeśli nie masz kontroli nad kontenerem, do którego chcesz podłączyć bazę danych, możesz spróbować zaczekać na określony port.

W tym celu używam małego skryptu, aby czekać na określony port udostępniony przez inny kontener.

W tym przykładzie, mójserwer będzie czekać na porcie 3306 z mydb pojemniku być osiągalny.

# 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

Dokumentację dotyczącą czekania na to skrypt można znaleźć tutaj

nie? Nie
źródło
Próbowałem użyć wait-for-it.sh wcześniej, ale zastępuje domyślny plik Dockerfile, prawda? Jak wygląda entrypoint.sh?
John Kariuki
Punkt wejścia zależy od twojego obrazu. Możesz to sprawdzić za pomocą docker inspect <image id>. Powinno to poczekać, aż usługa będzie dostępna i zadzwonić do punktu wejścia.
nono
Czy to jest w porządku ? Czy rozumiesz?
nono
Ma sens. Tak.
John Kariuki
6
Ostrzeżenie: MySQL 5.5 (prawdopodobnie także nowsze wersje) może odpowiadać podczas inicjalizacji.
Blaise,
8

Cześć za prostą kontrolę stanu przy użyciu docker-compose v2.1 , użyłem:

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

Zasadniczo uruchamia proste mysqlpolecenie, SHOW DATABASES;używając jako przykładu użytkownika rootz hasłem rootpasswd w bazie danych.

Jeśli komenda się powiedzie, baza danych jest gotowa do działania, więc ścieżka kontroli kondycji. Możesz użyć, intervalwięc testuje w odstępach czasu.

Po usunięciu drugiego pola ze względu na widoczność, oto jak by to wyglądało w twoim 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
Sylhare
źródło
1
Ostrzeżenie: w przypadku „wersji 3” pliku redagowania obsługa „warunków” nie jest już dostępna. Zobacz docs.docker.com/compose/compose-file/#depends_on
BartoszK
1
Powinieneś używać funkcji poleceń razem ze skryptem wait-for-it.sh . Ja robię to w ten sposób:command: ["/home/app/jswebservice/wait-for-it.sh", "maria:3306", "--", "node", "webservice.js"]
BartoszK
@BartoszKI tego nie rozumiem. Czy mógłbyś dodać pełną odpowiedź ze szczegółami? Mam dokładnie ten sam problem, ale nie mogę sprawić, żeby to zadziałało.
Thadeu Antonio Ferreira Melo
Upewnij się, że używasz wersji 2.1, w przeciwnym razie postępuj zgodnie z nowymi wytycznymi dotyczącymi wersji 3.0 i nowszych.
Sylhare
1
--execute \"SHOW DATABASES;\"co sprawiło, że
czekało
6

Zmodyfikowałem docker-compose.ymlzgodnie z poniższym przykładem i zadziałało.

  mysql:
    image: mysql:5.6
    ports:
      - "3306:3306"
    volumes:       
      # Preload files for data
      - ../schemaAndSeedData:/docker-entrypoint-initdb.d
    environment:
      MYSQL_ROOT_PASSWORD: rootPass
      MYSQL_DATABASE: DefaultDB
      MYSQL_USER: usr
      MYSQL_PASSWORD: usr
    healthcheck:
      test:  mysql --user=root --password=rootPass -e 'Design your own check script ' LastSchema

W moim przypadku ../schemaAndSeedDatazawiera wiele schematów i plików sql do inicjowania danych. Design your own check scriptmoże być podobny do następującego select * from LastSchema.LastDBInsert.

Podczas gdy zależny od sieci kod kontenera to

depends_on:
  mysql:
    condition: service_healthy
Mukesh Agarwal
źródło
Może to zadziałać, ale nie jestem pewien, czy jest to obsługiwane we wszystkich silnikach MySQL.
halfpastfour.am
Mówię o silnikach baz danych, takich jak InnoDB, MyISAM itp. Czy LastSchema.LastDBInsertdomyślny silnik MySQL jest specyficzny?
halfpastfour.am
Nie, to też nie jest wartość domyślna w mysql. To była tylko próbka. fikcyjne zapytanie.
Mukesh Agarwal
5
Ostrzeżenie: w przypadku „wersji 3” pliku redagowania obsługa „warunków” nie jest już dostępna. Zobacz docs.docker.com/compose/compose-file/#depends_on
BartoszK
4

Miałem ten sam problem, stworzyłem w tym celu zewnętrzny skrypt basha (jest on inspirowany odpowiedzią Maxima). Zastąp mysql-container-namenazwą kontenera MySQL, a także potrzebne jest hasło / użytkownik:

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... 😴"
  sleep 1
done

W moim MakeFile wywołuję ten skrypt zaraz po docker-composewywołaniu:

wait-for-mysql: ## Wait for MySQL to be ready
    bin/wait-for-mysql.sh

run: up wait-for-mysql reload serve ## Start everything...

Następnie mogę wywołać inne polecenia bez błędu:

Wystąpił wyjątek w sterowniku: SQLSTATE [HY000] [2006] Serwer MySQL zniknął

Przykład danych wyjściowych:

docker-compose -f docker-compose.yaml up -d
Creating network "strangebuzzcom_default" with the default driver
Creating sb-elasticsearch ... done
Creating sb-redis              ... done
Creating sb-db                 ... done
Creating sb-app                ... done
Creating sb-kibana             ... done
Creating sb-elasticsearch-head ... done
Creating sb-adminer            ... done
bin/wait-for-mysql.sh
MySQL is unavailable - waiting for it... 😴
MySQL is unavailable - waiting for it... 😴
MySQL is unavailable - waiting for it... 😴
MySQL is unavailable - waiting for it... 😴
MySQL is unavailable - waiting for it... 😴
MySQL is unavailable - waiting for it... 😴
MySQL is unavailable - waiting for it... 😴
MySQL is unavailable - waiting for it... 😴
mysqld is alive
php bin/console doctrine:cache:clear-metadata
// Clearing all Metadata cache entries
[OK] Successfully deleted cache entries.

Usunąłem kontrolę stanu, ponieważ jest teraz bezużyteczna w tym podejściu.

Cewka
źródło
3

RESTART ON-FAILURE

Ponieważ condition: service_healthywersja 3 nie jest już dostępna. Chodzi o to, że programista powinien zaimplementować mechanizm odzyskiwania po awarii w samej aplikacji. Jednak w przypadku prostych przypadków prostym sposobem rozwiązania tego problemu jest użycie restartopcji.

Jeśli status usługi mysql powoduje, że Twoja aplikacja do exited with code 1Ciebie trafia, możesz skorzystać z jednej z restartdostępnych opcji polityki. na przykład,on-failure

version: "3"

services:

    app:
      ...
      depends_on:
        - db:
      restart: on-failure
Hamid Asghari
źródło
2

Dodanie zaktualizowanego rozwiązania do metody sprawdzania kondycji. Prosty fragment:

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; }

Objaśnienie : Ponieważ mysqladmin pingzwraca fałszywe alarmy (szczególnie w przypadku nieprawidłowego hasła), zapisuję dane wyjściowe do zmiennej tymczasowej, a następnie używam grepdo znalezienia oczekiwanego wyniku ( mysqld is alive). Jeśli zostanie znaleziony, zwróci kod błędu 0. Jeśli nie zostanie znaleziony, drukuję całą wiadomość i zwracam kod błędu 1.

Rozszerzony fragment:

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

Wyjaśnienie : używam sekretów Dockera zamiast zmiennych env (ale można to również osiągnąć za pomocą zwykłych zmiennych env). Użycie $$jest dla $znaku dosłownego, który jest usuwany podczas przesyłania do kontenera.

Wyjście z docker inspect --format "{{json .State.Health }}" db | jqróżnych okazji:

Wszystko w porządku:

{
  "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 nie działa (jeszcze):

{
  "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"
    }
  ]
}

Złe hasło:

{
  "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"
    }
  ]
}
Maxim_united
źródło