Вопрос или проблема
Я запускаю контейнер 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
, как правило, указывает на то, что приложение не может установить сетевое соединение с сервером базы данных, скорее всего, из-за неправильно заданного хоста или условий подключения.
Анализ текущей конфигурации
-
Настройка хоста базы данных
- Использование
MARIADB_HOST: "127.0.0.1"
недопустимо, так как в рамках Docker все контейнеры работают в отдельных изолированных сетях. Поэтому "localhost" или "127.0.0.1" в контексте PHP-контейнера относится к самому контейнеру, а не к контейнеру с MariaDB. - Правильным решением будет указать имя службы базы данных, т.е.
MARIADB_HOST: db
, что он правильно отсылает запросы к контейнеру базы данных.
- Использование
-
Проблема с CMD в Dockerfile
- CMD, указанный в Dockerfile в форме массива, (в данном случае
[ "php","src/init.php"]
) не выполняет интерполяцию переменных окружения. Это также объясняет, почему запуск из консоли работает, а использование CMD — нет. Контейнер не видит переменные окружения, определенные в docker-compose.yml. - Решением может стать изменение CMD на команду оболочки, например,
[ "sh", "-c", "php src/init.php" ]
.
- CMD, указанный в Dockerfile в форме массива, (в данном случае
Рекомендации по устранению ошибки
-
Исправьте конфигурацию docker-compose.yml:
environment: MARIADB_HOST: "db" MARIADB_USER: root MARIADB_PASSWORD: top_very_secret MARIADB_DB: apidb
-
Обновите Dockerfile:
Вместо:CMD ["php","src/init.php"]
Используйте:
CMD ["sh", "-c", "php src/init.php"]
-
Проверка зависимостей:
Убедитесь, что 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 обрабатывает команды и переменные окружения. Это не только повысит стабильность вашего приложения, но и позволит более эффективно управлять вашей средой разработки.