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

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

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


Это наша будущая CDN из 5 серверов, которая будет раздавать контент на весь мир

Зачем настраивать собственный CDN


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

 

Готовимся к запуску


Для реализации задуманного нам понадобится:
  1. Несколько серверов в разных регионах, можно виртуальных (VPS)
  2. Отдельный субдомен. В нашем примере это будет cdn.nashsait.org
  3. geoDNS сервис, с помощью которого пользователь, обращаясь к субдомену, будет направлен на ближайший в его регионе сервер

Арендуем сервера и настраиваем geoDNS


Сперва определимся, где преимущественно находится основная пользовательская аудитория. В нашем примере это Казахстан, поэтому мы определенно должны иметь точку присутствия в этом регионе, чтобы у большинства пользователей всё работало максимально быстро. Остальные сервера будут «разбросаны» по миру. Арендовать их удобнее всего у хостинг-провайдеров, предлагающих сразу несколько локаций для размещения.

Мы закажем 5 виртуальных серверов с 25GB диска, безлимитным трафиком и последним Debian или Ubuntu:

Казахстан, ip: 86.104.73.235

Нидерланды, ip: 94.232.245.17

США, ip: 45.89.53.214

Бразилия, ip: 95.164.5.110

Япония, ip: 5.253.41.115

Чтобы пользователь при обращении к cdn.nashsait.org направлялся на ближайший для него сервер, нам нужен рабочий DNS с функцией geoDNS. Его можно настроить самому или использовать готовый сервис, например СlouDNS.

Мы будем использовать СlouDNS: регистрируемся, выбираем тариф GeoDNS и в личном кабинете добавляем новую DNS-зону, указав наш главный домен nashsait.org. В процессе создания зоны будет предложено выбрать для домена будущие NS-сервера. Отметим все доступные и на будущее скопируем их себе в отдельный текстовый файлик.

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

Затем для субдомена cdn.nashsait.org нужно создать несколько A-записей, каждая из которых в зависимости от региона пользователя будет указывать на один из наших CDN серверов. В качестве регионов можно указывать континенты, страны или отдельные штаты (для США и Канады). Начнем с Южной Америки и направим все запросы оттуда на сервер в Бразилии:


Сделаем то же самое для других регионов, при этом один из них рекомендуется добавить как регион «по умолчанию». В итоге список A-записей будет выглядеть так:



Самая нижняя запись «Default» означает, что все остальные, не указанные в других записях регионы (Европа, Африка, спутниковый интернет и т.п.), будут направляться на сервер в Нидерландах.

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

Осталось зайти на сайт регистратора нашего домена и изменить NS-сервера для nashsait.org на те, что мы ранее скопировали в отдельный текстовый файл.

 

Добавление SSL сертификатов


Чтобы CDN работал по протоколу HTTPS, мы установим бесплатный SSL сертификат от Let's Encrypt. Это удобно делать с помощью ACME Shell скрипта, который позволяет валидировать домен по DNS через ClouDNS API.

Достаточно установить acme.sh на одном из серверов, а затем скопировать полученный сертификат на все остальные. Выполним установку на сервере в Нидерландах:

root@cdn:~# wget -O - https://get.acme.sh | bash; source ~/.bashrc

Стоит заметить, что во время установки создается отдельная CRON задача для автоматического обновления сертификатов в будущем.

При выдаче сертификата проверка домена будет происходить через DNS, а необходимые для этого записи будут автоматически добавлены на ClouDNS через их API. Поэтому в учетной записи на ClouDNS в меню «API&Resellers» нам нужно создать нового API пользователя, придумав для него пароль. Полученный <auth-id> с паролем укажем в файле ~/.acme.sh/dnsapi/dns_cloudns.sh (не перепутайте с похожим dns_clouddns.sh). Вот строки файла, которые нужно раскоментировать и отредактировать:

CLOUDNS_AUTH_ID=<auth-id>
CLOUDNS_AUTH_PASSWORD="<пароль>"

Далее запустим получение SSL сертификата для cdn.nashsait.org

root@cdn:~# acme.sh --issue --dns dns_cloudns -d cdn.nashsait.org --server letsencrypt --reloadcmd "service nginx reload"

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



Запомним эти пути, они будут нужны при копировании сертификата на другие CDN локации и для настройки веб-сервера. Ошибка «Reload error for» не существенна и при дальнейшем обновлении сертификатов на полностью настроенном сервере ее не будет.

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

root@cdn:~# mkdir -p /root/.acme.sh/cdn.nashsait.org_ecc/
root@cdn:~# scp -r root@94.232.245.17:/root/.acme.sh/cdn.nashsait.org_ecc/* /root/.acme.sh/cdn.nashsait.org_ecc/

Это копирование нужно сделать регулярным, поэтому на этих же четырех серверах в CRON добавляем ежедневный запуск команды:
scp -r root@94.232.245.17:/root/.acme.sh/cdn.nashsait.org_ecc/* /root/.acme.sh/cdn.nashsait.org_ecc/ && service nginx reload

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

 

Настройка Nginx


На всех пяти CDN точках, в качестве веб-сервера для раздачи контента мы установим Nginx и настроим его как кэширующий proxy-сервер:

root@cdn:~# apt update
root@cdn:~# apt install nginx

Дефолтный файл конфига /etc/nginx/nginx.conf заменим на приведенный ниже:
nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 4096;
    multi_accept on;
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    types_hash_max_size 2048;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    access_log off;
    error_log /var/log/nginx/error.log;

    gzip on;
    gzip_disable "msie6";
    gzip_comp_level 6;
    gzip_proxied any;
    gzip_vary on;
    gzip_types text/plain application/javascript text/javascript text/css application/json application/xml text/xml application/rss+xml;
    gunzip on;            

    proxy_temp_path    /var/cache/tmp;
    proxy_cache_path   /var/cache/cdn levels=1:2 keys_zone=cdn:64m max_size=20g inactive=14d;
    proxy_cache_bypass $http_x_update;

server {
  listen 443 ssl;
  server_name cdn.nashsait.org;

  ssl_certificate /root/.acme.sh/cdn.nashsait.org_ecc/fullchain.cer;
  ssl_certificate_key /root/.acme.sh/cdn.nashsait.org_ecc/cdn.nashsait.org.key;

  location / {
    proxy_cache cdn;
    proxy_cache_key $uri$is_args$args;
    proxy_cache_valid 90d;
    proxy_pass https://nashsait.org;
    }
  }
}


В этом конфиге отредактируем:
  • max_size — размер кэша, не больше доступного на диске места
  • inactive — срок хранения кэшированных файлов, к которым не было обращений
  • ssl_certificate и ssl_certificate_key — абсолютные пути к файлам SSL сертификата
  • proxy_cache_valid — срок хранения кэшированных файлов
  • proxy_pass — URL сайта, с которого CDN загружает и кэширует файлы. У нас это nashsait.org
Обратите внимание на схожесть директив inactive и proxy_cache_valid. Чтобы не запутаться, рассмотрим их на простом примере. Вот что происходит при inactive=14d и proxy_cache_valid 90d:
  • если файл не будет запрошен в течение 14 дней, то он удаляется
  • если файл будет запрашиваться хотя бы раз в 14 дней, то он начнет считаться устаревшим только по истечении 90 дней, и после этого срока при очередном запросе Nginx загрузит его по новой с оригинального сервера
Указав нужные значения в nginx.conf, применим их:

root@cdn:~# service nginx reload

Следует отметить, что Nginx не будет кэшировать данные, если они получены с оригинального сервера с какими-либо cookies (заголовок «Set-Cookie»). Игнорировать это условие можно, добавив в конфиг следующие директивы:

proxy_ignore_headers "Set-Cookie";
proxy_hide_header "Set-Cookie";

На этом настройка завершена. Дополнительно можно создать bash-скрипт для очищения кэша:
purge.sh
#!/bin/bash
if [ -z "$1" ]
then
    echo "Purging all cache"
    rm -rf /var/cache/cdn/*
else
    echo "Purging $1"
    FILE=`echo -n "$1" | md5sum | awk '{print $1}'`
    FULLPATH=/var/cache/cdn/${FILE:31:1}/${FILE:29:2}/${FILE}
    rm -f "${FULLPATH}"
fi


Запуск этого скрипта удалит весь кэш, отдельный файл удаляется так:

root@cdn:~# ./purge.sh /test.jpg

 

Тестируем наш CDN


С помощью онлайн ping-сервисов можно проверить пинги к нашей сети доставки контента из разных мест:
Место запуска Host IP Время, мсек
Германия, Франкфурт cdn.nashsait.org 94.232.245.17 9.6
Испания, Мадрид cdn.nashsait.org 94.232.245.17 37.5
США, Чикаго cdn.nashsait.org 45.89.53.214 16.5
США, Атланта cdn.nashsait.org 45.89.53.214 23.8
Бразилия, Сан-Паулу cdn.nashsait.org 95.164.5.110 1.01
Казахстан, Астана cdn.nashsait.org 86.104.73.235 21.9
Южная Корея, Сеул cdn.nashsait.org 5.253.41.115 31.5
Пинг хороший, теперь разместим в корне основного сайта картинку test.jpg и на сервисе Ping-Admin посмотрим на скорость ее загрузки через CDN:
Место запуска IP Время, сек Скорость загрузки
Бразилия, Сан-Паулу 95.164.5.110 0,233338 12,37 МБ/с
США, Ашберн 45.89.53.214 0,130310 11,24 МБ/с
США, Чикаго 45.89.53.214 0,222213 5,19 МБ/с
Канада, Монреаль 45.89.53.214 0,180502 5,51 МБ/с
Япония, Токио 5.253.41.115 0,091587 43,88 МБ/с
Гонконг 5.253.41.115 0,532564 1,56 МБ/с
Казахстан, Алматы 86.104.73.235 0,195468 4,86 МБ/с
Великобритания, Хэмпшир 94.232.245.17 0,191698 6,57 МБ/с
Нидерланды, Налдвейк 94.232.245.17 0,156096 11,67 МБ/с
Франция, Париж 94.232.245.17 0,301658 4,15 МБ/с
Всё работает, контент раздается быстро. Теперь у нас есть собственный рабочий CDN с безлимитным трафиком и точками присутствия на всех континентах.
 

Комментарии (13)


  1. MaxStirlits
    24.04.2024 22:21
    +6

    А потом раздать эти сервера через cloud flare, чтоб наверняка :)


  1. Ksoo
    24.04.2024 22:21
    +7

    Спасибо за статью, про geoDNS интересно.

    А про свой CDN: теперь обвешать это все мониторингами, написать инструкции как это обслуживать, добавлять сервера, оплачивать хостинги, назначить ответственных. Реализовать инвалидацию, конвертацию картинок. Посмотреть сколько времени в месяц это все жрет, влететь пару раз на "забыли оплатить сервер", "у хостера технические работы". И понять что не нужен тебе свой CDN сервер.


    1. tuxi
      24.04.2024 22:21

      Конвертация и инвалидация изображений это да, попаболь на бесплатном nginx, приходится делать свои костыли.


    1. Catemiko
      24.04.2024 22:21

      Можно создать свой GeoDNS сервер на основе, например, PowerDNS и делегировать поддомены на него.


  1. mOlind
    24.04.2024 22:21
    +3

    Сертификаты копировать не обязательно. Каждый может сервер получать свои сертификаты через acme.sh, когда вы делаете авторизацию через dns. Потом их надо установить и перезагрузить nginx. Это автоматизируется командой: acme.sh --install-cert -d <domain> --key-file /etc/nginx/ssl/certs/<domain>.key --fullchain-file /etc/nginx/ssl/certs/<domain>.cert --reloadcmd "sudo /usr/sbin/service nginx reload" пути внутри nginx произвольные. главное чтоб в конфигах домена вы их правильно указали.

    С такой командой acme.sh будет автоматом проверять обновление сертификата и перезагружать его внутри nginx, когда сертификат обновится.

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

    Мы для Guru Maps настроили кэширующиего worker-a на cloudflare и он заменил нам все сервера во всех регионах. Cloudflare стал edge cdn и дальше усложнять архитектуру не пришлось.


    1. perezanov Автор
      24.04.2024 22:21

      Спасибо, добавлено пару слов о DNS Failover, как возможное решение


    1. olku
      24.04.2024 22:21

      На Caddy реверс в строк 10.


  1. akakoychenko
    24.04.2024 22:21
    +2

    Вангую, что работать будет как-попало. Недаром, серьезные люди не полагаются на DNS, а один IP анонсируют в куче точек присутствия одновременно, чтобы балансировать на уровне маршрутизации пакетов, а не DNS запросов.

    Кажется, если делать из говна и палок без ресурсов серьезных людей, то хотябы надо 2-х шаговый алгоритм реализовать, чтобы на первом шаге, балансируя по DNS, запрос шел на скрипт-распределитель (у которого единый url глобально), который, в свою очередь, уже будет, проверяя по базе ip, отдавать 302 на что-то вроде europe.cdn.nashsait.org, чтобы "неправильный" роутинг по DNS приводил лишь к первому медленному запросу, а не всем в рамках сессии


    1. perezanov Автор
      24.04.2024 22:21

      По личному опыту такого неправильного роутинга не более 2-3%. На чистом Anycast, насколько знаю, тоже может быть погрешность, но своей AS увы нет чтобы утверждать


    1. olku
      24.04.2024 22:21

      Не зря продают GlobalIP а не резольвинг.


  1. yrub
    24.04.2024 22:21

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


    1. akakoychenko
      24.04.2024 22:21

      Ддос да, но CDN от взлома и брутфорса? Где им место в content delivery?


      1. yrub
        24.04.2024 22:21

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