SQLSTATE [HY000] [2002] Установление соединения отклонено в контейнере PHP Apache Docker с использованием mariadb, если подключено через CMD в Dockerfile (PHP CLI работает)

Вопрос или проблема

Я запускаю контейнер Docker с:

  • PHP 8.1
  • Apache 2.4
  • MariaDB (последний официальный образ docker)

Он запускается без проблем. Я не могу подключиться к базе данных контейнера Docker через PDO.

Dockerfile:

FROM php:8.1-apache

WORKDIR /var/www/html/

RUN pecl install xdebug \
    && apt update \
    && apt install libzip-dev -y \
    && docker-php-ext-enable xdebug \
    && a2enmod rewrite \
    && docker-php-ext-install zip \
    && rm -rf /var/lib/apt/lists/* \
    && docker-php-ext-install pdo pdo_mysql

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
COPY composer.json .

RUN groupadd -r user && useradd -r -g user user
USER user
RUN composer install --no-dev

COPY . .

EXPOSE 80

CMD ["php","src/init.php"]

docker-compose.yml:

services:

  php:
    build: ./php
    depends_on:
      - db
      - adminer
    container_name: php-apache
    ports:
      - 80:80
    volumes:
      # настройка xdebug для возможности использования отладчика PHP
      - ./php/conf.d/xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
      - ./php/conf.d/error_reporting.ini:/usr/local/etc/php/conf.d/error_reporting.ini
      # конфигурация apache (имя сервера)
      - ./apache/apache2.conf:/etc/apache2/apache2.conf
      # конфигурация apache (правило переписывания для маршрутизации всех запросов к неизвестным ресурсам через REST контроллер)
      - ./apache/000-default.conf:/etc/apache2/sites-enabled/000-default.conf
      # Исходный код
      - ./php/src:/var/www/html/src
      # отключить локальные компоненты composer
      - /php/vendor
      - /php/composer.lock
      - /php/composer.phar
    environment:
      MARIADB_HOST: "127.0.0.1"
      MARIADB_USER: root
      MARIADB_PASSWORD: top_very_secret
      MARIADB_DB: apidb

  adminer:
    image: adminer
    depends_on:
      - db
    restart: always
    ports:
      - 8080:8080

  db:
    image: mariadb
    container_name: db
    volumes:
      - maria-db-storage:/var/lib/mysql
    environment:
      MARIADB_ROOT_PASSWORD: top_very_secret
      MARIADB_DATABASE: apidb
    ports:
      - 3306:3306

volumes:
  maria-db-storage:

Я пробовал localhost и 127.0.0.1 в качестве значения переменной окружения MARIADB_HOST. Это не решило проблему.

Следующий php код (содержимое src/init.php):

new PDO(
    "mysql:host={$_ENV['MARIADB_HOST']};dbname={$_ENV['MARIADB_DB']}",
    $_ENV['MARIADB_USER'],
    $_ENV['MARIADB_PASSWORD'],
    [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    ]
);

Всегда приводит к:

SQLSTATE[HY000] [2002] Connection refused.

ОБНОВЛЕНИЕ

Я сделал шаг вперед, заменив эту строку здесь:

MARIADB_HOST: "127.0.0.1"

на имя моего сервиса базы данных; то есть:

MARIADB_HOST: db

Я также выяснил, что могу войти на панель управления adminer только если использую db в качестве хоста. Это было догадкой, и мне бы хотелось понять, почему это работает?

Тем не менее, это все еще не работает полностью. Это работает только если я опускаю строку CMD в Dockerfile, строю контейнер, вхожу в его терминал через docker exec -t -i php-apache /bin/bash и запускаю команду php src/init.php оттуда. Если я пытаюсь сделать это через CMD из Dockerfile (как описано выше), я получаю упомянутое сообщение об ошибке.

Что мне все еще не хватает?

Я бы, конечно, предпочел автоматизировать этот вызов инициализации при запуске контейнера Docker. Продолжаю разбираться…

Теперь я понял. Соединение было отклонено, потому что переменные окружения использовались только в том случае, если скрипт выполнялся через оболочку, а через CMD Docker’а – как я его использовал – никакой обработки оболочки не происходило. В документации я также нашел это:

В отличие от оболочнoй формы, форма exec не вызывает командную оболочку. Это означает, что обычная обработка оболочки не происходит. Например, CMD [ “echo”, “$HOME” ] не будет производить замену переменной для $HOME. Если вы хотите, чтобы происходила обработка оболочки, используйте оболочную форму или запускайте оболочку напрямую, например: CMD [ “sh”, “-c”, “echo $HOME” ]. При использовании формы exec и прямом выполнении оболочки, как в случае оболочной формы, – это оболочка, которая выполняет расширение переменной окружения, а не docker.

Когда используется в оболочном или exec формате, инструкция CMD задает
команду, которая будет выполнена при запуске образа.

Если вы используете оболочную форму CMD, то будет выполнено
в /bin/sh -c

Используя:

CMD [ "sh", "-c", "php src/init.php" ]

вместо:

CMD [ "php", "src/init.php" ]

Сработало!

Ответ или решение

Ошибка SQLSTATE [HY000] [2002] Connection refused в контейнере PHP Apache с MariaDB: диагностика и решение

Разработка приложений с использованием контейнеров Docker — это эффективный способ управления зависимостями и средами, однако, как и в любом технологическом процессе, внедрение может сопровождаться ошибками. В этом ответе мы сосредоточимся на распространенной ошибке подключения к базе данных, которая проявляется как SQLSTATE [HY000] [2002] Connection refused. Мы разберем проблему более подробно, а также предоставим рекомендации по ее устранению на примере конфигураций Dockerfile и docker-compose.yml.

Описание проблемы

При запуске контейнера с приложением, написанным на PHP 8.1 с использованием Apache и MariaDB, у вас возникает ошибка при подключении к базе данных, когда вы пытаетесь выполнить код:

new PDO(
    "mysql:host={$_ENV['MARIADB_HOST']};dbname={$_ENV['MARIADB_DB']}",
    $_ENV['MARIADB_USER'],
    $_ENV['MARIADB_PASSWORD'],
    [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    ]
);

Ошибка Connection refused, как правило, указывает на то, что приложение не может установить сетевое соединение с сервером базы данных, скорее всего, из-за неправильно заданного хоста или условий подключения.

Анализ текущей конфигурации

  1. Настройка хоста базы данных

    • Использование MARIADB_HOST: "127.0.0.1" недопустимо, так как в рамках Docker все контейнеры работают в отдельных изолированных сетях. Поэтому "localhost" или "127.0.0.1" в контексте PHP-контейнера относится к самому контейнеру, а не к контейнеру с MariaDB.
    • Правильным решением будет указать имя службы базы данных, т.е. MARIADB_HOST: db, что он правильно отсылает запросы к контейнеру базы данных.
  2. Проблема с CMD в Dockerfile

    • CMD, указанный в Dockerfile в форме массива, (в данном случае [ "php","src/init.php"]) не выполняет интерполяцию переменных окружения. Это также объясняет, почему запуск из консоли работает, а использование CMD — нет. Контейнер не видит переменные окружения, определенные в docker-compose.yml.
    • Решением может стать изменение CMD на команду оболочки, например, [ "sh", "-c", "php src/init.php" ].

Рекомендации по устранению ошибки

  1. Исправьте конфигурацию docker-compose.yml:

    environment:
      MARIADB_HOST: "db"
      MARIADB_USER: root
      MARIADB_PASSWORD: top_very_secret
      MARIADB_DB: apidb
  2. Обновите Dockerfile:
    Вместо:

    CMD ["php","src/init.php"]

    Используйте:

    CMD ["sh", "-c", "php src/init.php"]
  3. Проверка зависимостей:
    Убедитесь, что MariaDB полностью запущен перед попыткой подключения. Вы можете использовать depends_on в docker-compose.yml, но это не гарантирует, что служба действительно готова принимать соединения. Для более надежного подхода стоит добавить логику повторной попытки подключения в ваш код PHP.

Проверка и отладка

После внесения изменений попробуйте пересобрать ваши контейнеры:

docker-compose down
docker-compose up --build

Следите за логами контейнера PHP на наличие ошибок подключения. Вы можете сделать это с помощью команды:

docker logs php-apache

Также можете проверить соединение с MariaDB из командной строки, выполнив:

docker exec -it php-apache bash

и затем:

php -r "new PDO('mysql:host=db;dbname=apidb', 'root', 'top_very_secret');"

Заключение

Следуя приведенным рекомендациям, вы сможете устранить ошибку SQLSTATE [HY000] [2002] Connection refused. Важно правильно настроить параметры окружения и понимать, как Docker обрабатывает команды и переменные окружения. Это не только повысит стабильность вашего приложения, но и позволит более эффективно управлять вашей средой разработки.

Оцените материал
Добавить комментарий

Капча загружается...