Когда на одном сервере в 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.

· полезно?
АЧ
Алексей Чикун веб-разработчик, команда TRIOHEAD

Делаю сайты и сервисы целиком: от вёрстки до сервера. Пишу о том, что проверил на своих проектах.

Комментарии

// загружаю комментарии…

Комментарии публикуются после проверки. Ссылки и HTML не принимаются.