company_banner

Как мы сэкономили 2000 USD на трафике из Amazon S3 с помощью nginx-кэша

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

    Проблема и как её решать

    Имеются следующие вводные:

    • Объем статики — около 1 ТБ;

    • Исходящий трафик — около 15 ТБ в месяц;

    • Текущий ежемесячный счет — около 2000 USD.

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

    Что с этим можно сделать?

    1. воспользоваться услугами различных CDN-провайдеров;

    2. реализовать собственный кэширующий прокси.

    От CDN’ов отказались по двум причинам:

    • В CDN как таковом не было потребности: у проекта единственный целевой регион пользователей. К тому же, точек присутствия известных CDN’ов в данном регионе не было (и даже близко).

    • Стоимость. Все популярные решения выходили в среднем от 300 USD до тысяч у крупных провайдеров.

    Примерная стоимость услуг популярных CDN для нашего случая получалась такая:

    • Selectel — 250 USD;

    • Akamai — 350 USD;

    • Amazon CloudFront — 1000+ USD;

    • Google CDN — 1000+ USD.

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

    Реализация

    Архитектура

    Основная проблема, которая возникала при локальном кэширующим прокси, — отказоустойчивость. Так как данных много и отдавать их надо быстро, под хранение требуется сервер на быстрых NVMe-дисках с достаточным объемом. Главная забота в таком случае — потеря самого сервера: ведь тогда мы полностью теряем статику, что никуда не годится.

    Очевидное решение — докупить второй сервер и сделать балансировку между ними, а отказоустойчивость на основе VRRP. Так получилось бы нормальное решение и в плане отказоустойчивости, и в плане масштабируемости. Однако при обсуждении вопроса с клиентом пришли к выводу, что для нас это избыточно, поскольку объем трафика не требует масштабирования и в обозримом будущем не планируется существенного увеличения (т.е. мы бы получили существенное увеличение стоимости без явной на то потребности). В итоге остановились на минимальном варианте: в случае потери кэширующего сервера достаточно делать автопереключение обратно на публичный S3-бакет. С таким подходом получаем действительно экономное и в достаточной мере отказоустойчивое решение (повторюсь, что в нашем частном случае).

    Итоговая схема выглядит так:

    Детали

    Весь трафик мы получаем в единую точку входа: nginx-балансировщики. После этого запросы уходят в Kubernetes и попадают по назначению в nginx на выделенном сервере-хранилище. Если по какой-то причине существуют проблемы в получении ответа от нашего прокси, включается backup upstream — в его роли origin.

    Могут появиться резонные вопросы: «Зачем делать этот прокси узлом Kubernetes? Это же не приносит никакой пользы в данной схеме!». Всё довольно просто: мы сторонники подхода IaC и для нас есть вполне ощутимая польза: унификация конфига и управления новой сущностью. В случае, когда это отдельный хост (живущий сам по себе), им надо как-то управлять, ставить туда nginx, конфигурировать его и т.д. А потом забудут о нём и о том, из какого репозитория он управляется (если он вообще появится)… Kubernetes в наших реалиях автоматически решает все эти боли и не приносит проблем или накладных расходов, а управляется IaC-манифестами из Git-репозитория.

    Как выглядят конфиги для этой схемы?

    Вот что в Nginx LB:

    ##################
    
    server {
        listen 80;
        server_name MAIN.IMG.DOMAIN;
    
        location / {
            proxy_set_header Host $http_host;
            proxy_pass http://s3-upstream;
            proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
        }
    }
    
    ##################
    
    upstream s3-upstream {
        server k8s-ingress-ip-1:30080 max_fails=1 fail_timeout=5s weight=100;
        server k8s-ingress-ip-2:30080 max_fails=1 fail_timeout=5s weight=100;
        server k8s-ingress-ip-3:30080 max_fails=1 fail_timeout=5s weight=100;
    
        server 127.0.0.1:8888 backup;
    }
    
    ###################
    
    server {
        listen 127.0.0.1:8888 default_server;
        server_name DOMAIN.NAME;
    
        location / {
            resolver 8.8.8.8 valid=10s;
            set $bucket "BUCKET_NAME.s3.eu-central-1.amazonaws.com";
            proxy_set_header Host $bucket;
            proxy_pass http://$bucket;
        }
    }

    Здесь есть пара очень важных моментов, на которые стоит обратить внимание:

    1. Конструкция из нескольких server'ов. В чем её смысл? Если в качестве backup upstream указать напрямую адрес бакета, то рано или поздно мы столкнёмся с проблемой того, что адрес будет преобразован в IP во время загрузки конфигурации nginx и закэширован. Но IP в URL'е бакета — динамический, а следовательно, nginx ничего не узнает о том, когда адрес изменится, поэтому в момент переключения на backup upstream начнет отправлять трафик на неправильный IP.

      Чтобы избежать этой фатальной ошибки, приходится делать промежуточный internal server_name, в котором указываем resolver и время жизни полученного IP. Также стоит сделать его default_server, чтобы не получить проблем с Host-заголовком, что пробрасывается из origin'а.

    2. Директива proxy_next_upstream. Если вы её не укажете, переключение на backup upstream будет происходить почти наверняка не так, как вы того хотите.

    Далее конфиг самого прокси-кэша (точнее, только значимые его части):

    proxy_cache_path /var/cache/nginx/s3-cache levels=1:2 keys_zone=s3-cache:1280m max_size=1050g inactive=43200m;
    
    location / {
        add_header 'Access-Control-Allow-Origin' '*';
        proxy_cache s3-cache;
        proxy_cache_key $scheme$request_method$host$request_uri;
        proxy_cache_valid  200 301 302 60d;
        proxy_cache_valid  403 404     1m;
         proxy_cache_revalidate on;
         proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_403 http_404 http_429;
         proxy_cache_background_update on;
         proxy_cache_lock on;
    
        expires 60d;
    
        proxy_http_version     1.1;
        proxy_hide_header      x-amz-id-2;
        proxy_hide_header      x-amz-request-id;
        proxy_hide_header      x-amz-version-id;
        proxy_ignore_headers   "Set-Cookie" "Expires" "Cache-Control";
    
        proxy_pass http://BUCKET_NAME.s3.eu-central-1.amazonaws.com/;
    }

    Тут всё должно быть понятно.

    NB: Кстати, существует более сложный и неочевидный вариант конфигурации nginx при использовании приватного S3-бакета — мы его уже описывали в другой статье.

    Ingress-ресурс не прилагаю, т.к. он выглядит совершенно буднично.

    Результат

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

    Получается, что вся связка стоила нам лишь добавления одного сервера стоимостью 60 евро и бесплатного трафика, который отдается с него, в текущих объемах. При этом есть ещё двукратный запас по росту трафика до того момента, когда он может начать оплачиваться. А про прежний счёт в 2000 USD можно забыть благодаря тому, что с разовым «прогревом» кэша S3-трафик пропадает (а стоимость хранения данных в S3 минимальна).

    Выводы

    Что мы поняли после эксплуатации подобного решения на протяжении некоторого времени? Эта реализация имеет простую схему отказоустойчивости и подходит даже для больших объемов и нагрузок. Однако надо понимать ее потенциальные недостатки и что стоит учесть перед боевым внедрением.

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

    Второй момент — объем трафика. Учитывайте особенности тарификации своего провайдера. Поскольку точка раздачи по сути единственная, объемы могут быть значительными и «неожиданно» могут привести к немалой трате средств. Тогда уж лучше задуматься про подключение CDN.

    В-третьих, отказоустойчивость. Вам почти наверняка не удастся воспроизвести отказоустойчивость, которую сможет предложить популярный CDN. Если вы сейчас работаете в рамках одного ЦОДа, то точек отказа — великое множество. Даже если вы продумаете хорошую схему с механизмом failover’а входных балансировщиков, в машинном зале ЦОДа может отказать БП, а трактор перед ЦОДом обязательно перекопает магистральный канал… Очевидно, что ради отказоустойчивости одной только раздачи статики нет смысла городить межЦОДовую архитектуру — тогда тоже проще подключить CDN. Однако если у вас уже такая инфраструктура, почему бы и нет?.. (И опять же, не забудьте про регион доставки контента.)

    Такие потенциальные проблемы, их следствия и пересечения разнятся от ситуации к ситуации, поэтому критично учитывать узкие места архитектуры в целом и, конечно, понимать, какую цель вы хотите достичь и технически, и для бизнеса.

    P.S.

    Читайте также в нашем блоге:

    Флант
    DevOps-as-a-Service, Kubernetes, обслуживание 24×7

    Комментарии 27

      0
      Не написали, во сколько вышло это решение? Действительно ли S3 дороже, чем «простое решение»? Либо было использованы существующие мощности, и таким образом «сэкономили» 2000 USD?
        +6
        Вроде бы написали же :)

        Получается, что вся связка стоила нам лишь добавления одного сервера стоимостью 60 евро и бесплатного трафика, который отдается с него, в текущих объемах. При этом есть еще двукратный запас по росту трафика до того момента, когда он может начать оплачиваться.
          0
          Пропустил, спасибо!
        +4
        Присматриваю проектик вне работы. На амазон у нас хостятся примерно 300 фалов по 2-4гб, которые нужно доставлять клинетам. В месяц выходило порядко 1200-2500 долл.

        Оставили s3 как source of truth, добавили автоматическую поддержку зеркал — добавляешь в админке, он автоматом синхронизирует зеркала. Добавили балансировку между зеркалами.

        Как зеркало пока используем DigitalOcean Spaces. Постепенно раз в месяц увеличивает траффик на зеркало. Уже сейчас заметна разница в счетах (примерно в 2 раза меньше), но для статистики нужно погонять больший срок.
          +2
          Конструкция из нескольких server'ов. В чем её смысл?

          Если честно, то в вашей конструкции нет смысла. IP не будет кешироваться и при более простой конструкции, без лишних локейшнов.
          location / {
              resolver 1.1.1.1.;
              set $s3_dns s3.eu-central-1.amazonaws.com;
              proxy_pass $s3_dns;
            0
            Ну так мы именно этот метод и использовали в backup'ном апстриме. Или как вы предложили бы это реализовать без него?
            +1
            недорогие CDN не рассматривали? 15 ТБ трафика = $150, есть и дешевле
              +3
              У cloudflare до 100тб можно бесплатно CDN использовать.
              И вместо s3 и cdn можно было взять сервер за 40 евро на хетзнер и раздавать хоть 300тб и хранить 5.
                +1
                а можно ссылочку на «У cloudflare до 100тб можно бесплатно»? Везде где я видел, они обещают любой объем бесплатно, при условии соблюдения «Section 10: Limitation on Non-HTML Caching of our Terms of Service» и всего остального из www.cloudflare.com/terms
                +1
                Перешли с s3 на cloudflare. Платили как вы, сейчас ничего не платим. На настройку ушло полчаса.
                Конечно можно было взять сервак в хецнере за 60 евро, но облака и сервисы мы любим в том числе за отказоустойчивость, а с одним сервером отказоустойчивость такая себе, особенно за 60%.
                Можно было взять за те же деньги дешёвый cdn на scaleway/hetzner/kamatera/ovh и не париться.
                  +1

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

                  +1
                  То есть вся экономия за счет того что сервер у хостера с бесплатным траффиком?
                    +4
                    Еще раз доказывает, что AWS — это дорого. И что есть «скрытые» платежи, в этом случае это была оплата трафика, которым обычно пренебрегают. Вначале проекта может действительно был 1Гб и оплата казалась минимальной.
                      +2
                      И что есть «скрытые» платежи, в этом случае это была оплата трафика, которым обычно пренебрегают.

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


                      Еще раз доказывает, что AWS — это дорого.

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

                        0

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

                          0

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

                            0

                            Так это вы всё про надежность — распределенная БД, алерты, разве нет?


                            А в целом я больше про сервисы типа EC2 и управляемые РСУБД, про миграцию с десятка (V)DS без переписывания кучи кода

                        0

                        Очень. Но удобно и надежно.
                        Вообще, удивляет конечно разница в стоимости инстансов. Когда пришли дешевые процессоры Амд разница в цене была 10%. Не знаю как сейчас. Уже даже 64 ядра подвезли, должно бы подешеветь.

                          0
                          >AWS — это дорого
                          Для хостинга одного сервера — дорого. Для хостинга сложной системы с разными компонентами, динамической нагрузкой, возможностью поднимать тестовые конфигурации — вполне нормально.

                          >И что есть «скрытые» платежи
                          Они не скрытые. Но, как и во многих других вещах, AWS сложнее legacy хостинга.
                            0
                            AWS сложнее legacy хостинга.

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

                              0
                              две стороны одной монеты :-)
                                +1

                                По ощущениям, чтобы осознанно выбирать надо потратить месяцы на изучение документации по всем имеющимся возможностям. И это без "поиграть", а предсказать счёт — вообще нереально.

                                  0
                                  это без "поиграть", а предсказать счёт — вообще нереально.

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

                            0

                            Амазон это не про "дёшево", а про "надёжно". Понятно что свое "на коленке" за 60$ тоже будет работать, но вы условно теряете то ради чего нужен S3 — масштабируемость, простота в обслуживании.

                              0
                              .., а отказоустойчивость на основе VRRP.

                              это шутка, да? — VRRP в AWS?

                                0

                                Видимо, в хетцнере, на который хватило денег клиента )

                                +1

                                Возникала ли необходимость инвалидации кеша, отдельных файлов?

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

                                Самое читаемое