Когда на одном сервере в Docker живут десятки сайтов, у них общая сеть, и имена в ней — общие. Две ловушки этой сети мы встретили на своих серверах: имя базы данных одного сайта готово было перехватить запросы к нему от соседей (заметили при запуске, до того как выстрелило), а сайт не смог обратиться к самому себе по своему же домену.
Это седьмая часть цикла «Сервер для WordPress».
Ловушка 1: база с именем домена
Шаблон compose, с которого начинались наши сайты, давал контейнеру базы сетевой псевдоним — имя домена сайта. Удобно: PHP ходит в базу по адресу site.ru, и по нему же легко понять, чья это база.
mariadb:
networks:
globalnetwork:
aliases:
- ${HOST} # например, site.ru
Пока HOST — тестовый поддомен, это безобидно. Но когда так поднимается сайт на корне домена, в общей сети появляется имя alekseychikun.ru, которое указывает на базу данных. Другие контейнеры этой сети, обращаясь к сайту по домену, получают адрес MariaDB вместо сайта.
У нас это ударило бы по конкретной вещи: этот сайт — сервер обновлений моих плагинов, и соседние сайты на том же сервере спрашивают у него о новых версиях. Их запросы ушли бы в базу.
Решение — у базы нет сетевого имени вовсе, PHP ходит в неё через сокет в общем томе:
mariadb:
volumes:
- mysqlsocket:/var/run/mysqld
# без networks: база не видна в общей сети совсем
php:
volumes:
- mysqlsocket:/var/run/mysqld
define('DB_HOST', 'localhost:/var/run/mysqld/mysqld.sock');
Правило: псевдонимы в общей сети — только для того, к чему действительно должны обращаться соседи. База к ним не относится.
Ловушка 2: сайт не видит сам себя
Прогреву кэша нужно запросить страницы своего же сайта. Очевидный способ — wp_remote_get(home_url('/')). Из контейнера PHP он не работает: cURL error 7, соединение не установлено.
Домен указывает на внешний адрес сервера. Запрос из контейнера уходит на этот адрес и должен развернуться обратно в ту же машину. Без отдельной настройки маршрутизатора такой разворот не проходит.
Решение — не ходить через внешний адрес вовсе, а обращаться к контейнеру nginx по его имени в сети Docker, подставляя нужный домен в заголовок:
$res = wp_remote_get('http://site-nginx-1' . $path, [
'headers' => [
'Host' => 'site.ru',
'X-Forwarded-Proto' => 'https', // без него WordPress ответит редиректом на https
],
'timeout' => 30,
]);
- Host — чтобы nginx и WordPress поняли, какой это сайт, и ключ кэша совпал с ключом настоящих посетителей;
- X-Forwarded-Proto: https — снаружи сайт работает по HTTPS, и без этого заголовка WordPress ответит 301 на HTTPS-адрес, а страница в кэш не попадёт.
Подробно про устройство обходчика — в 30-й части цикла про кэш страниц. А о том, что берётся из заголовка Host и почему это опасно, — в 25-й части.
Как проверить свою сеть
# кто в общей сети отвечает на имя домена
docker run --rm --network globalnetwork alpine nslookup site.ru
# видит ли PHP сам себя через внешний адрес
docker exec site-php-1 curl -sS -o /dev/null -w '%{http_code}\n' https://site.ru/
Если на имя домена в общей сети отвечает что-то, кроме прокси на входе, — это псевдоним, который стоит убрать.
Несколько сайтов на одном сервере, и что-то странно ходит по сети?
Пришлите compose-файлы — проверю имена и маршруты внутри Docker.
// загружаю комментарии…