Использование CDN для статичных файлов в Drupal 8

CDN (Content Delivery Network) — это распределенная инфраструктура для доставки статичных файлов, имеющая ряд преимуществ по сравнению с привычными способами хранения.

Физически CDN — это несколько географически распределенных файловых и проксирующих серверов, оптимизированных для скачивания файлов, в том числе в очень больших объемах.

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

Так же технология позволяет эффективно справляться даже с очень большими нагрузками. Ну и как правило, стоимость хранения данных в CDN существенно ниже, чем на веб хостинге.

Еще одной особенностью CDN, но уже скорее ограничением, является эффективность работы только со статичными файлами, например изображениями, видео и прочим контентом.

Небольшое вступление

Я занимаюсь поддержкой открытого проекта Метро 2-х столиц, и уже достаточно давно собирался выложить его исходный код в открытый доступ, чтобы любой желающий смог развернуть проект у себя и, при желании, помочь в разработке.

Основной объем сайта составляют фото, порядка 2 Гб. Да, объем по современным меркам не большой, но в систему контроля версий его не загрузишь. Можно было бы попробовать использовать проксирование, но мне всегда хотелось отвязать контент от файлов сайта, и это ли не хорошая задача для CDN?

Вот вам и первый сценарий использования.

Так же отмечу, что в нашей студии 4D design мы поддерживаем множество проектов, в том числе хостим на своих серверах, разворачиваем по несколько копий проекта для тестирования и публикаций обновлений, и перенос статических файлов в CDN может существенно упростить процесс работы с проектами, где используется много файлового контента.

Еще один очевидный сценарий — очень большие объемы данных, которые было бы просто не рентабельно хранить на сервере вместе с самим сайтом.

Краткий обзор CDN провайдеров

CDN сейчас очень популярен и востребован, предложений много, есть из чего выбрать. Наиболее известные провайдеры — это, пожалуй, Cloudflare, KeyCDN, Amazon CloudFront.

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

Я же для себя выбрал решение от DigitalOcean — Spaces. На DigitalOcean (далее DO) мы хостим наши проекты, доверяем ему свои данные и пока проблем не было, все четко и просто. Со Spaces все тоже довольно просто, один тариф — 5$ в месяц за 250 Гб.

Кстати, для новых пользователей DO сейчас доступны приветственные 100$, которые действуют в течение 2-х месяцев. Так же их можно получить, если зарегистрироваться по моей реферальной ссылке.

Настройка DigitalOcean Spaces CDN

На самом деле Spaces — это хранилище объектов (файлов), а встроенный CDN — это дополнительная возможность, которая, кстати, появилась не сразу. Похожий принцип используется и в Amazon CloudFront, где в роли хранилища выступает сервис Amazon Simple Storage Service (S3).

Забегая вперед, скажу, что Spaces использует протокол S3 для загрузки файлов в хранилище, но давайте сначала активируем и настроим само хранилище.

Создание Space

Процесс довольно простой. Нужно выбрать регион основного центра хранения данных, тип хранилища — публичный или приватный и уникальное название для хранилища. На момент публикации статьи было доступно 3 региона: США, Сингапур и Нидерланды. Так же есть возможность сразу же активировать функцию CDN.

Добавление поддомена для CDN

Очень удобно, что можно сразу создать синоним CDN на своем домене, тут же добавить новый поддомен или выбрать существующий и даже создать для него сертификат. Не знаю как у других провайдеров, но в данном случае все делается в одном окне.

Хранилище создано, и мы можем увидеть адреса, по которым к нему можно обратиться. Самое важное тут:

  • "Endpoint", у меня это ams3.digitaloceanspaces.com;
  • Название хранилища, оно же "bucket", у меня это 4dd;
  • Регион, у меня это ams3;
  • Синоним, он же поддомен, у меня это https://cdn.4dd.pw.

И осталось только получить пару ключ/секрет для доступа на загрузку файлов с помощью сторонних клиентов. Это делается в разделе "API", секция "Spaces access keys".

Файлы в хранилище можно загружать через веб-интерфейс, с помощью API, с помощью сторонних клиентов — Transmit, Cyberduck, FileZilla Pro, консольного клиента s3cmd и прочих, поддерживающих протокол S3. Мы же будем интегрироваться с Drupal.

Если останутся вопросы можно воспользоваться шикарной документацией.

Использование CDN в Drupal 8

Общий принцип использования CDN для файлов сайта будет такой: загрузить файлы в CDN и сделать так, чтобы URL на сайте указывали на эти файлы.

Варианты реализации:

  • Вариант 1: если URL файлов формируются вручную, например трудолюбивым контент менеджером, то и загружать файлы придется тоже вручную, но думаю, что это редкий случай.
  • Вариант 2: если URL файлов формируются Drupal, например при загрузке файла через поле, то в идеале, конечно, хотелось бы, чтобы Drupal загружал в CDN и сам файл.
  • Вариант 3: URL файлов на сайте заменяются на URL файлов в CDN, но о загрузке самих файлов вам придется позаботиться самому.

Модуль CDN для Drupal

Первое что нашлось в интернете по теме — это модуль CDN. Как оказалось в нем реализован третий вариант, то есть модуль ничего в CDN не загружает и никак с ним не взаимодействует. Вы можете управлять только тем, какие именно URL будут заменяться.

Этот вариант можно автоматизировать, например примонтировав CDN в папку, в которую загружаются файлы. Вот проект, который позволяется реализовать такую схему.

Возможно, это будет самый быстрый вариант по скорости, но мне он показался не самым удобным, и я продолжил свои исследования.

Модуль S3 File System

S3 File System — это второй по популярности модуль имеющий версию для Drupal 8, после модуля CDN. В нем используется второй вариант, при этом вся папка публичных (и/или приватных) файлов может быть перенесена в CDN. Такой подход интересен тем, что помимо файлов, загружаемых в поля, из CDN будут так же передаваться файлы CSS и JS. Это может быть и преимуществом, и недостатком, в зависимости от того, как часто эти файлы обновляются на сайте. Как рекомендуют в самом модуле, кеш шаблонов при этом лучше вынести в другую папку, с помощью настройки в файле settings.php (там же прописывается пара ключ/секрет для доступа Drupal к CDN на загрузку файлов).

Альтернативный вариант — использовать хранилище только для определенных полей. Этот вариант, пожалуй, наиболее актуальный.

Несмотря на статус альфа, модуль довольно интересный, правда мне не удалось проверить его в боевых условиях.

Обновление

Сначала я остановился на решении описанном в следующей части статьи, но потом все же решил вернуться к этому модулю. Для сохранения естественного порядка повествования, рекомендую сначала прочитать две следующих части, а потом вернуться к тексту ниже.

Неприемлемая скорость загрузки некешированных страниц при использовании модулей Flysystem, сподвигла меня более детально рассмотреть и опробовать этот модуль.

Принципиально модули не сильно отличаются, даже используют одинаковое название для хранилища по умолчанию — s3 (это, кстати, позволило мне очень быстро переключиться на этот модуль). Но в S3 File System используется другая библиотека — AWS SDK.

Основные настройки модуля производятся в интерфейсе администрирования, а зависимые от окружения — в файле settings.php.

Основные настройки модуля S3 File System

Здесь указываются имеющиеся у нас параметры хранилища: я использовал настройку "Use a Custom Host", где указал свой "endpoint". Также указаны "bucket", синоним, корневая папка, а вот регион в случае использования Custom Host — не играет роли.

Настройки в файле settings.php:

$settings['s3fs.access_key'] = 'YOURLONGKEY';
$settings['s3fs.secret_key'] = 'YOURVERYVERYLONGSECRET';

Для конфигурации с переносом всей папки публичных (и/или приватных) файлов нужно добавить еще один или несколько параметров, но так как тут могут быть различные нюансы, то я, пожалуй, не буду их приводить, это хорошо описано в readme-файле к модулю.

На этом настройка завершена, правда для полноценной работы потребовалось применить патч, который решает проблему со ссылками при первичной генерации стилей изображений.

В этом модуле более основательно подошли к проблеме медленной работы со стилями и, как я понял, тут реализован механизм, при котором сначала отображаются изображения из временных локальных файлов, а после загрузки в хранилище — уже из хранилища. И это работает действительно быстро.

Как и в модуле Flysystem, проблема не удаляемых после загрузки временных файлов, к сожалению, тут тоже имеет место быть.

Модули Flysystem и Flysystem - S3

Модуль Flysystem и расширение к нему Flysystem - S3 мне показались наиболее перспективным решением. Тут также применятся второй вариант работы с CDN, реализована поддержка хранилищ для определенных полей, а в качестве кодовой базы используется многообещающая библиотека Flysystem.

Помимо S3 поддерживаются и другие протоколы, что может быть актуально если CDN провайдер его не поддерживает. Так что это решение выглядит и более универсальным.

Так как для своего проекта я остановился на этом модуле, то приведу пример его настройки. Производится она в файле settings.php:

$schemes = [
  's3' => [
    'driver' => 's3',
    'config' => [
      'key'    => 'YOURLONGKEY',
      'secret' => 'YOURVERYVERYLONGSECRET',
      'region' => 'ams3',
      'bucket' => '4dd',
      'protocol' => 'https',
      'prefix' => 'metro2',
      'cname' => 'cdn.4dd.pw',
      'endpoint' => 'https://ams3.digitaloceanspaces.com',
      'public' => TRUE,
    ],
    'cache' => TRUE,
  ],
];

$settings['flysystem'] = $schemes;

Еще один пример есть в документации к модулю Filebrowser.

Здесь нам понадобились имеющиеся у нас данные: "endpoint", "bucket", регион, синоним и пара ключ/секрет. Так же обратите внимание, что я задал дополнительный префикс metro2, это просто корневая папка, чтобы не смешивать файлы от разных проектов.

Теперь s3 можно выбрать в качестве хранилища для файловых полей. Правильные URL к CDN файлам будут формироваться автоматически, без дополнительных модулей, а так как я указал еще и синоним, то и выглядеть они будут просто замечательно.

Обновление

Использование модуля показало, что механизм генерации стилей изображений работает очень медленно. Из-за этого странички после очистки кеша могут загружаться по 10 и даже более секунд, что, конечно же, неприемлемо.

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

Еще одна проблема — при генерации стилей изображений, файлы во временной папке, которые передавались потом в хранилище, не удалялись после закачки. А после очистки кеша добавлялись новые экземпляры. За пару дней работы сайта временная папка довольно прилично раздулась, и пока мне не удалось найти решение этой проблемы.

Перенос существующих файлов в CDN

Описанная выше конфигурация будет работать только для новых полей и новых загруженных в них файлов. Если же нужно изменить хранилище и перенести файлы для существующего поля, то штатными средствами это сделать не получится.

Возможно, сейчас в меня полетят камни от коллег, но я предпочел подправить пути в базе данных, чем создавать новое поле и загружать заново все файлы через интерфейс. Последовательность действий будет следующая:

  1. Скачать куда-нибудь текущие файлы. У меня они предусмотрительно хранились в папке images, внутри папки sites/default/files.
  2. Закачать скачанные файлы в CDN так, чтобы путь соответствовал новому положению файлов, в моем случае — это metro2/images, где metro2 — дополнительный префикс, который я указал.

    Не забудьте также указать правильные права доступа к файлам, если это публичные файлы, то доступ на чтение должен быть открыт всем.

  3. Найти в базе данных, в таблице file_managed записи для нужных файлов и заменить им хранилище в поле uri на s3. В моем случае исходным значением было public, и я переносил все файлы внутри папки images, поэтому просто сделал замену всем записям начинающимся с public://images:

    UPDATE file_managed
    SET uri = REPLACE(uri, 'public://images', 's3://images');
    
  4. В настройках поля, выбрать новое хранилище, чтобы новые файлы загружались сразу в CDN. Если выбор хранилища в интерфейсе неактивен, что скорее всего так и будет, то значение можно подправить через экспорт и импорт конфигурации. Например для поля filed_image файл конфигурации будет field.storage.node.field_image.yml, и в нем нужно заменить значение параметра uri_scheme на s3.

  5. Удалить перенесенные файлы из sites/default/files.
  6. (опционально) Если вы используете стили изображений, то можно попробовать перенести сразу сгенерированные Drupal файлы изображений. Путь к файлам там будет немного отличаться, так как он содержит папку с названием хранилища, но разобраться будет не сложно. Если не переносить сгенерированные изображения, то Drupal сгенерирует новые и загрузит их в CDN при первом обращении к изображению со стилем.

Примечание

Для модуля S3 File System есть команда Drush s3fs-copy-local и кнопка в интерфейсе "Copy local public files to S3", которые позволяют автоматически перенести файлы в хранилище. Данная возможность работает только для конфигурации хранилища для всей папки локальных (и/или приватных) файлов.

Небольшая оптимизация

Подсмотрел в модуле CDN, что для более быстрой установки соединения с CDN можно добавить две директивы внутрь тега head:

<link rel="dns-prefetch" href="https://CDNDOMAIN.COM">
<link href="https://CDNDOMAIN.COM" rel="preconnect" crossorigin>

Я это сделал внутри файла MYTHEME.theme в папке темы оформления:

/**
 * Implements hook_preprocess_HOOK() for HTML document templates.
 */
function MYTHEME_preprocess_html(&$variables) {

  $dns_prefetch = [
    '#tag' => 'link',
    '#attributes' => [
      'rel' => 'dns-prefetch',
      'href' => 'https://CDNDOMAIN.COM',
    ],
  ];

  $preconnect = [
    '#tag' => 'link',
    '#attributes' => [
      'rel' => 'preconnect',
      'href' => 'https://CDNDOMAIN.COM',
      'crossorigin' => TRUE,
    ],
  ];

  $variables['page']['#attached']['html_head'][] = [$dns_prefetch, 'dns-prefetch'];
  $variables['page']['#attached']['html_head'][] = [$preconnect, 'preconnect'];
}

Вот и все, на этом процесс настройки заканчивается.

Возможные проблемы

Может об этом стоило упомянуть раньше, но скорость работы Drupal c файлами в CDN существенно ниже, чем с локальными. Они дольше загружаются в поля, дольше генерируются стили изображений. Для каких-то проектов это может быть весьма существенно, но в моем случае файлы загружаются не пользователями, поэтому на них это никак не отражается. Просто имейте ввиду, что такой недостаток есть и, если это критично, то есть смысл рассмотреть другие варианты интеграции с Drupal или другие протоколы загрузки файлов в CDN.

Примечание

Описанная выше проблема относится к опыту использования модуля Flysystem. С модулем S3 File System такой проблемы нет.

Так как файлы в CDN располагаются на другом домене, то на них будут действовать более строгие политики взаимодействия с файлами. За это отвечает технология CORS (Cross-origin resource sharing), параметры которой могут быть частично настроены в панели управления Spaces, но на данный момент, возможности этих настроек выглядят весьма скромно. Хотя может я просто не разобрался как это правильно делать. В общем, тоже имейте это ввиду, если делаете какие-либо операции с файлами в своем коде.

Выводы

Я очень доволен проделанной работой и в будущем собираюсь применять данный подход для проектов, на которых имеются мало-мальски существенные объемы файлового контента.

Использование CDN — это один из способов повысить скорость загрузки сайта, и такая оптимизация определенно понравится пользователям, сделает интернет чуточку быстрее, а мир чуточку лучше!