Всем привет!

Данная заметка является примером реализации задачи, возникшей в одном из проектов. Сам проект представляет собой небольшой сайт-магазин, где потребовалось ускорить загрузку статического контента (карточек и изображений).

Исходя из этого, первым делом я решил двигаться в сторону CDN от Cloudflare, так как ранее уже имел с ним опыт работы. Также я отказался от хранения изображений на сервере и перенёс всё в S3 от Hetzner.

Однако в процессе тестирования этой связки выяснилось, что S3-сервис, предоставляемый Hetzner, довольно сырой и не поддерживает работу с Cloudflare Proxy. Стоит отметить, что на Reddit можно встретить немало негативных отзывов об этом S3-сервисе, в том числе из-за его высокой стоимости.

Теоретически его можно настроить для работы через Cloudflare Proxy, добавив заголовок Host или подменив SNI. Но эти возможности доступны только в Enterprise-подписке.

В качестве альтернативного, но более дорогого варианта можно использовать промежуточную VPS для перенаправления запросов на S3. Решение сомнительное, но допустимое.

В результате поисков и по совету коллег было принято решение попробовать аналогичный S3-сервис от DigitalOcean. К моему удивлению, он оказался крайне удобным и простым в настройке.

Об этом теперь подробнее…

Подготовка - аккаунты и утилиты

Для настройки тестового сетапа потребуется зарегистрировать Free Tier-аккаунт на DigitalOcean, который предоставляет возможность попробовать их сервисы. По условиям предоставляется 60 дней и 200 кредитных долларов на аккаунт. При регистрации потребуется привязать платежную карту, с которой спишется $12 (позже деньги вернутся). К сожалению, скипнуть этот момент невозможно.

Доступ к S3-корзинам будет предоставляться через Cloudflare Proxy. Для работы с ним достаточно иметь бесплатный аккаунт. Соответственно, также нужно будет зарегистрироваться.

Чтобы взаимодействовать с хранилищем, в документации DigitalOcean рекомендуется использовать CLI-утилиту — s3cmd.

Утилита устанавливается из стандартных репозиториев и представляет собой программу-клиент для взаимодействия с S3 при помощи команды:

# На ArchLinux
sudo pacman -Sy s3cmd

# На MacOS
brew install s3cmd

# На Ubuntu
sudo apt-get install s3cmd

Также для тестирования и проверки бакетов понадобится cURL. Настройку s3cmd выполним позже.

Для Windows есть графические утилиты — S3 Browser или Cyberduck.

Первый бакет

На главной странице своего DO аккаунтапереходим в раздел Spaces Object Storage и нажимаем кнопку Create Bucket: dos3-new-bkt1.png

Нас перебросит на новую страницу настройки корзины. Там выбираем дата-центр, где хотим разместить нашу корзину. Далее нажимаем кнопку, включаем CDN, указываем имя бакета и создаём его. dos3-new-bkt2.png

После этого корзина будет создана. Заливать файлы в неё можно простым перетаскиванием или перемещением на страницу браузера в панели. Однако мы будем работать с корзиной через терминал, соответственно, требуется создать ключи доступа к бакету. dos3-new-bkt3.png

Чтобы создать ключи, переходим в сетевые настройки бакета — Settings — и нажимаем кнопку Create Access Key в разделе с ключами. dos3-new-bkt4.png

Отобразится новое окно, в котором нужно выбрать права доступа. Выбираем All Permissions (Bucket & Objects), убеждаемся в корректности выбранного бакета и создаём ключ. dos3-new-bkt5.png

В production имеет смысл выделять более ограниченный набор прав для конкретного бакета.

После этого нас перебросит в настройки бакета, и в разделе ключей отобразится наш ключ. В параметрах ключа копируем в KeePass — Access Key ID и Secret Key. dos3-new-bkt6.png

Работа с бакетом через терминал

Перед началом произведем настройки утилиты - s3cmd,

s3cmd --configure

После ввода команды, запуститься интерактивная оболочка, где нужно будет указать данные подключаения к s3.

Сначала указываем Access/Secret токены:

Access Key []: XXXXXxxxx
Secret Key []: XXXXXxxxx

Далее потребуется указать регион, площадку. У меня это Франквут:

Default Region []: fra1

В следующем этапе указываем адрес S3:

S3 Endpoint []: fra1.digitaloceanspaces.com

Затем указываем шаблон URL-адреса для доступа к вашей корзине. Поскольку DO Spaces поддерживает URL-адреса конечных точек на основе DNS.

DNS-style bucket+hostname:port template for accessing a bucket []: %(bucket)s.fra1.digitaloceanspaces.com

Последующие пункты настраивают GPG и ожидают пароль для шифрования файлов с целью защиты от чтения. Нам это не нужно, поэтому просто нажимаем Enter.

Encryption password:
Path to GPG program [/usr/bin/gpg]:

На вопрос об использовании HTTPS по умолчанию установлено значение Yes — соглашаемся.

Use HTTPS protocol [Yes]:

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

HTTP Proxy server name:

В конце мы соглашаемся, что хотим протестировать доступ к S3:

Test access with supplied credentials? [Y/n] Y

Ожидаемый результат:

Please wait, attempting to list all buckets...
Success. Your access key and secret key worked fine :-)

При успешном результате увидите подобное сообщение.

В завершение соглашаемся на сохранение конфигурации. Конфигурационный файл будет расположен в домашней директории пользователя:

ls ~/.s3cfg
--
/home/tony/.s3cfg

Убедится в том, что все работает можем командой:

s3cmd la
---
2025-01-25 15:25       203290  s3://devs3bkt1/botlogo.png

Данная команда отобразит список доступных нам объектов (корзинок и файликов).

Теперь загрузим картинки с файлухи сервера на s3:

s3cmd put -r storage/ s3://devs3bkt1/storage/

Описание агрументов:

  • put - команда для загрузки (upload) файлов или папок в указанный бакет S3.
  • -r - флаг, означающий рекурсивную загрузку (все файлы и поддиректории из storage/ будут загружены).
  • storage/ - локальная директория, которая загружается в S3.
  • s3://devs3bkt1/storage/ - путь в бакете DigitalOcean Spaces куда будут загружены файлы.

Аналогичная команда:

s3cmd sync storage/ s3://devs3bkt1/storage/

По сути работает также, как и s3cmd put -r.

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

s3cmd setacl --acl-public -r s3://devs3bkt1/storage/

В завершении этой главы возвращаемся в панельку DO, и копируем URL-адрес (CDN Endpoint). Он понадобится нам при настройке на стороне CF. dos3-new-bkt7.png

Проверить endpoint и убедится в том, что картинка отдается из кеша можно через curl запрос:

curl -I https://mybucket.nyc1.cdn.digitaloceanspaces.com/folder/1/logo.png
---
HTTP/2 200
date: Sat, 25 Jan 2025 17:06:05 GMT
content-type: image/png
content-length: 2744
last-modified: Sat, 25 Jan 2025 17:05:46 GMT
....
cf-cache-status: HIT  <-----

В HTTP-респонсе, ищем заголовок - cf-cache-status. Его значение HIT говорит нам, что в ресурс был отдан из кеша, без обращения к серверу.

Другие возможнозные значения могу означать:

Значение Описание
MISS Ресурс не найден в кеше, запросил его с исходного сервера и закешировал.
EXPIRED Кешированный ресурс устарел, запросил новый у сервера и обновил кеш.
BYPASS Кеширование отключено для данного ресурса (например, заголовком Cache-Control: no-cache).
DYNAMIC Ресурс определен как динамический и не кешируется (например, страницы с авторизацией).
STALE отдал устаревшую версию ресурса, так как новый пока недоступен.
REVALIDATED проверил у сервера, можно ли использовать кешированную версию.

Если при обращении к ресурсу вы получаете cf-cache-status: MISS, не стоит расстраиваться — кеширование может занять 3–5 минут.

Кастомный домен для CDN

Для CDN DigitalOcean предоставляет свой URL-адрес, но нам это не подходит — мы хотим раздавать всю статику со своего поддомена, например, cdn.nixhub.ru.

В настройках S3-бакета есть возможность добавить кастомный домен. Тут есть два варианта:

  1. Выпустить сертификат от Let’s Encrypt через панель DigitalOcean. Это будет работать только если ваш домен хостится в DO.
  2. Использовать уже имеющийся сертификат. Этот вариант подходит, если у вас уже есть ранее выпущенный сертификат. В таком случае его можно загрузить и использовать для своего домена. Например, сертификат от Let’s Encrypt тоже подойдёт.

Мы пойдём вторым путём, так как у меня уже есть wildcard-сертификат — я просто загружу его в панель.

Стоит отметить, что ранее DO Spaces* поддерживали сертификаты Cloudflare Origin SSL. Но на момент написания статьи и тестирования выяснилось, что эта возможность больше не поддерживается (это подтвердил технический саппорт после обращения).

Что бы добавить свой сертификат переходим в раздел настроек корзины, и меняем настройки CDN: dos3-new-bkt8.png Здесь мы добавим сертификат для нашего поддомена - cdn.nixhub.ru.

В новом окне переходим во вкладку Bring Your Own Certificate и заполняем поля: dos3-new-bkt9.png

  • Присваиваем имя сертификату.
  • Вставляем содержимое сертификата и ключа

После добавляния сертификата, в новом окне, поле - subdomain прописываем поддомен. И жмем на save. dos3-new-bkt10.png

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

CORS Настройка для S3

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

При хранении статического контента в S3, если ваш фронтенд (например, React, Vue.js) и пытается получить данные из бакета S3, браузер может заблокировать этот запрос. Чтобы разрешить доступ, необходимо настроить CORS.

Для любого S3 провайдера CORS - настройки представляют из себя json-структуру с описанием разрешенных http методов и заголовках.

С точки зрения настроек в DO, там все еще проще. Мы переходим в настройки нашей S3-корзины, и поле CORS Configurations жмем на кнопку добавить: dos3-new-bkt11.png

В новом окне добавляем поля: dos3-new-bkt12.png Согласно этим настройкам, любой сайт/ресурс сможет загружать файлы с нашего бакета - поле Origin: *. С точки зрения безопасности, это не всегда корректно и рекомендуется точечно настраивать разрешенные источники.

Здесь же мы разрешаем использовать запросы с методами - GET и HEAD. Буквально разрешаем скачивание файлов и просмотр их залоговков.

Ну и в конце указываем время кеширования, 86400 секунд (24 часа). После сохраняем настройки.

В интернетах довольно часто можно встретить рекомендованные настройки, такие:

[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET", "HEAD", "POST", "PUT"],
    "AllowedOrigins": ["https://cdn.nixhub.ru"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 86400
  }
]

Если вы не особо хотите заморачиваться относительно этого, можно использовать их..

Настройка CF Proxy

В админке Cloudflare переходим в настройки → DNSRecords и нажимаем кнопку Add Record. cf-new1.png Для новой записи необходимо указать следующие параметры:

  • Type: CNAME
  • Name: Здесь можете указать любое предпочтительное для вас имя. Например, cdn.example.com или img.example.com.
  • Target: В этом поле указываем адрес для CDN Endpoint, тот что мы сохранили ранее.

После настройки и небольшой задержки можно проверить работу связки с помощью cURL-запроса:

curl -I https://cdn.nixhub.ru/folder/1/logo.png

Если в ответе на запрос получаете 200 HTTP Сode, значит все работает корректно..


Рефералка в DO

Если вдруг вы планируете реализовать что-то подобное или решите зарегистрировать новый аккаунт, то у меня к вам небольшая просьба — сделать это по моей реферальной ссылке:

👉 https://m.do.co/c/9d7625bea525

С вашей стороны это никак не изменит условия — вы всё так же получите бесплатный Free Tier с 200$ на 60 дней для тестирования сервисов DigitalOcean. Ну а с моей стороны — мне начислят небольшие бонусы за рекомендацию 😊

Заранее спасибо! 🚀