Настройка nginx для раздачи статики и экономия места на диске

При разработке сайтов возникает проблема хранения множества изображений и прочих медиа-файлов. Такие медиа-файлы могут занимать ощутимое количество места на диске, которое в век повального применения ssd дисков становится дороже. Да и бекапить такие данные часто накладно. Кроме того эти файлы создаются один раз и хранятся всё время существования сайта (зачастую), и таскать эти файлы за собой с сервера на сервер с каждым годом становится неприятно. Знакомо? Давайте попробуем разобраться как дешево можно решить проблему.

Решение очень простое и называется — объектное хранилище. Яркий пример таких хранилищ — Amazon S3 или DigitalOcean Spaces. 

Буду рассказывать на примере DO, потому что сам пользуюсь этим провайдером.

Регистрируемся на сайте digitalocean, если вы ещё этого не сделали по каким-то причинам, выбираем сверху вкладку Spaces и видим такую вот страницу:

digitalocean spaces

Остается нажать Create a Space, и заполнить обязательные поля.

Затем необходимо создать ключи для программного доступа к хранилищу. Делается это выбором в верхнем меню надписи API. Пролистываете страницу до надписи Spaces access keys и жмете Generate New Key, вводите название ключа и получаете ключ и секрет

С предварительными ласками действиями покончено, можно приступить к настройкам сервера.

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

Допустим, все ваши статичные файлы хранятся по адресу http://example.com/static/. Мы настраиваем nginx таким образом, чтобы он проверял наличие файла сначала в этой директории (локальной для вашего сайта), и если его там нет, то он бы обращался во внешнее хранилище.

Вот пример конфигурации для nginx:

location ~ ^/static/(.*)$ {
        try_files $uri @storage;
}
location @storage {
        proxy_pass https://ip_addr:433/$1;
        proxy_connect_timeout                           60;
        proxy_send_timeout                                      60;
        proxy_read_timeout                                      60;
        proxy_redirect                                          off;
        proxy_buffer_size                                       4k;
        proxy_buffers                                           4 32k;
        proxy_busy_buffers_size                         64k;
        proxy_temp_file_write_size                      10m;
        proxy_hide_header       Strict-Transport-Security;
        add_header              X-Cache-Status $upstream_cache_status;
        proxy_ignore_headers    Set-Cookie;
        proxy_set_header        Host                    "yourname.ams3.digitaloceanspaces.com";
        proxy_set_header        X-Real-IP               $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_cache             cache_name;
        proxy_cache_valid       404 502 1m;
        proxy_cache_valid       any 24h;
        proxy_cache_revalidate  on;
        proxy_cache_lock        on;
}

где ip_addr — это IP адрес, который резолвится при обращении к yourname.ams3.digitaloceanspaces.com, а yourname — это имя, которое вы указывали при создании spaces на сайте DO.

Кроме того необходимо добавить в nginx.conf в секцию http вот такую строку:

proxy_cache_path /var/cache/nginx/cache_name levels=1:2 keys_zone=cache_name:10m inactive=48h max_size=10g;

где
/var/cache/nginx/cache_name — путь к кешу (должен иметь права на запись процессом nginx)
keys_zone=cache_name:10m — название кеша (которое используется в секции server для вашего сайта) и размер ключей кеширования 10 мегабайт (достаточно для хранения ключей от нескольких сотен тысяч файлов)
inactive=48h — срок годности файлов в кеше (48 часов)
max_size=10g — максимальный размер в 10 гигабайт кешированной информации

Кеш нам необходим для уменьшения кол-ва запросов к Spaces и для ускорения отдачи статики. Чем больше вы ставите размер кеша и неактивность, тем быстрее всё будет работать.

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

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

  • Используйте пакет из композера для работы с Spaces, это в разы проще, чем разбираться в API от S3 и писать всё с нуля. Шикарный код можно найти тут: https://flysystem.thephpleague.com/docs/ . Установка тоже простая:
    composer require league/flysystem-aws-s3-v3

    В скрипте стоит поставить паузу в 200-300мс между каждым копированием файла, потому что API имеет ограничение на частоту запросов и при копировании без задержек начинаются проблемы.

  • В скрипте можно копировать по N файлов за один цикл запуска, нет смысла занимать систему на несколько дней и копировать огромный объем файлов. Файлы могут по чуть-чуть копироваться и несколько недель, в этом нет ничего страшного. Nginx сам будет определять какой файл еще есть на диске, а какой уже в Spaces и отдавать его посетителю вашего сайта
  • После каждой итерации копирования не забывайте удалять файлы локально, освобождая место

Пример кода для подключения flysystem:

use Aws\S3\S3Client;
use League\Flysystem\AwsS3v3\AwsS3Adapter;
use League\Flysystem\Filesystem;

$client = S3Client::factory([
    'credentials' => [
        'key'    => 'your-key', // ваш ключ из API
        'secret' => 'your-secret', // ваш секрет из API
    ],
    'region' => 'ams3',  // вместо ams3 подставьте регион Spaces 
    'endpoint' => 'https://ams3.digitaloceanspaces.com', // вместо ams3 подставьте регион Spaces 
    'version' => 'latest',
]);

$adapter = new AwsS3Adapter($client, 'yourname', '/'); // yourname - имя spaces и / - путь относительно корня хранилища. Если меняете этот путь, то его необходимо изменить и в конфиге nginx
// например вот так: proxy_pass https://ip_addr:433/mysite/$1;
// $adapter = new AwsS3Adapter($client, 'yourname', '/mysite');
// это позволит вам в одном хранилище хранить файлы от нескольких сайтов

$filesystem = new Filesystem($adapter);

Надеюсь, этот мануал кому-нибудь поможет сэкономить деньги

Если есть замечания или вопросы — добро пожаловать в комментарии