company_banner

Как мы пробивали Великий Китайский Фаервол (ч.2)

    Привет!


    С вами снова Никита — системный инженер из компании SЕMrush. И этой статьей я продолжаю историю про то, как мы придумывали решение обхода Китайского Фаервола для нашего сервиса semrush.com.


    В предыдущей части я рассказал:


    • какие появляются проблемы после того, как принимается решение «Нам нужно сделать так, чтобы наш сервис работал в Китае»
    • какие проблемы есть у китайского интернета
    • зачем нужна ICP-лицензия
    • как и почему мы решили тестировать наши тестовые стенды с помощью Catchpoint
    • какой результат дал наш первый вариант решения, базирующийся на Cloudflare China Network
    • как мы нашли баг в DNS Cloudflare


    Эта часть — самая итересная, на мой взгляд, потому что сосредоточена на конкретных технических реализациях стейджингов. И начнем мы, а точнее продолжим, с Alibaba Cloud.


    Alibaba Cloud


    Alibaba Cloud — довольно большой облачный провайдер, в котором есть все сервисы, позволяющие ему честно величать себя cloud provider. Хорошо, что у них есть возможность зарегаться иностранным пользователям, и что большая часть сайта переведена на английский (для Китая это роскошь). В данном облаке можно работать со множеством регионов мира, материкового Китая, а также Океанической Азии (Гонконг, Тайвань, и т.д.).


    IPSEC


    Начали с географии. Так как тестовый сайт у нас находился в Google Cloud, нам необходимо было “связать” Alibaba Cloud c GCP, поэтому открыли список локаций, в которых присутствует Google. В тот момент у них еще не было своего датацентра в Гонконге.
    Ближайшим регионом оказался asia-east1 (Тайвань). У Ali наиболее близким регионом континентального Китая к Тайваню оказался cn-shenzhen (Шеньчжень).


    С помощью terraform описали и подняли всю инфраструктуру в GCP и Ali. Туннель 100 мбит/с между облаками поднялся практически моментально. На стороне Шеньчженя и Тайваня подняли проксирующие виртуалки. В Шеньчжене пользовательский трафик терминируется, проксируется через туннель в Тайвань, а оттуда уже идет напрямую на внешний IP нашего сервиса в us-east (Восточное побережье США). Пинг между виртуалками по туннелю 24ms, что не так плохо.


    Одновременно мы разместили тестовую зону в Alibaba Cloud DNS. После делегирования зоны на NS Ali, время резолвинга снизилось с 470 ms до 50 ms. До этого зона тоже была на Cloudlfare.


    Параллельно с туннелем до asia-east1 подняли еще один туннель из Шеньчженя прямо в us-east4. Там создали еще проксирующих виртуалок и начали мерить оба решения, маршрутизируя тестовый трафик с помощью Cookies или DNS. Схематично тестовый стенд описан на следующем рисунке:



    Latency для туннелей получилась следующей:
    Ali cn-shenzhen <--> GCP asia-east1 — 24ms
    Ali cn-shenzhen <--> GCP us-east4 — 200ms


    Браузерные тесты Catchpoint рапортовали об отличном улучшении показателей.



    Сравните результаты тестов для двух решений:


    Решение Uptime Median 75 Percentile 95 Percentile
    Cloudflare 86.6 18s 30s 60s
    IPsec 99.79 18s 21s 30s

    Это данные решения, использующего IPSEC-туннель через asia-east1. Через us-east4 результаты были хуже, да и ошибок было больше, поэтому результаты приводить не буду.


    По результатам данного теста двух туннелей, один из которых терминируется в ближайшем регионе к Китаю, а другой в финальном пункте назначения, стало понятно, что важно как можно быстрее “выныривать” из-под китайского фаервола, а дальше использовать быстрые сети (CDN-провайдеров, облачных провайдеров и т.д.). Не надо пытаться одним махом пройти фаервол и попасть в пункт назначения. Это не самый быстрый путь.


    В целом, результаты неплохи, однако, у semrush.com медиана 8.8s, а 75 Percentile 9.4s (на том же тесте).
    И прежде чем двигаться дальше, я хотел бы сделать небольшое лирическое отступление.


    Лирическое отступление


    После того, как пользователь заходит на сайт www.semrushchina.cn, который резолвится через “быстрые” китайские DNS-серверы, HTTP-запрос идет через наше быстрое решение. Ответ возвращается по тому же пути, но во всех JS-скриптах, HTML-страницах и прочих элементах вэб-страницы указан домен semrush.com для дополнительных ресурсов, которые должны быть загружены при отрисовке страницы. То есть клиент резолвит “главную” А-запись www.semrushchina.cn и идет в быстрый туннель, быстро получает response — HTML-страницу, в которой указано:


    • скачай такой-то js с sso.semrush.com,
    • CSS файлы забери с cdn.semrush.com,
    • и еще возьми картинок с dab.semrush.com
    • и так далее.

    Браузер начинает идти во “внешний” интернет за этими ресурсами, проходя каждый раз через пожирающий время ответа фаервол.


    Но на в предыдущем тесте представлены результаты, когда на странице нет ресурсов semrush.com, только semrushchina.cn, а *.semrushchina.cn резволвится в адрес виртуалки в Шеньчжене, чтобы попасть потом в туннель.


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


    Subfilter


    Решение родилось практически сразу после того, как всплыла данная проблема. Нам был нужен PoC (Proof of Concept), что наши решения прохода фаервола действительно работают хорошо. Для этого нужно по максимуму заворачивать весь трафик сайта в это решение. И мы применили subfilter в nginx.


    Subfilter — это довольно простой модуль в nginx, позволяющий менять одну строку в теле ответа на другую строку. Вот мы и поменяли все вхождения semrush.com на semrushchina.cn во всех ответах.


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



    В итоге, где клиент бы получил <subdomain>.semrush.com, он получал <subdomain>.semrushchina.cn и послушно шел через наше решение.


    Однако недостаточно просто поменять домен в одну сторону, ведь бэкэнды все также ожидают semrush.com в последующих запросах от клиента. Соответственно, на том же сервере, где делается замена в одну сторону, с помощью простенького регулярного выражения мы получаем поддомен из запроса, а дальше делаем proxy_pass с переменной $host, выставленной в $subdomain.semrush.com. Может показаться запутанно, но это работает. И работает хорошо. Для отдельных доменов, требующих другой логики, просто создаются свои server-блоки и делается отдельная конфигурация. Ниже представлены укороченные nginx конфиги для наглядности и демонстрации этой схемы.


    Следующий конфиг обрабатывает все запросы из Китая на .semrushchina.cn:

        listen 80;
    
        server_name ~^(?<subdomain>[\w\-]+)\.semrushchina.cn$;
    
        sub_filter '.semrush.com' '.semrushchina.cn';
        sub_filter_last_modified on;
        sub_filter_once off;
        sub_filter_types *;
    
        gzip on;
        gzip_proxied any;
        gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
    
        location / {
            proxy_pass http://127.0.0.1:8083;
            proxy_set_header Accept-Encoding "";
            proxy_set_header Host $subdomain.semrush.com;
            proxy_set_header X-Accept-Encoding $http_accept_encoding;
        }
    }

    Данный конфиг проксирует на localhost на 83 порт, а там поджидает следующий конфиг:


        listen 127.0.0.1:8083;
    
        server_name *.semrush.com;
    
        location / {
            resolver 8.8.8.8 ipv6=off;
            gunzip on;
            proxy_pass https://$host;
            proxy_set_header Accept-Encoding gzip;
        }
    }

    Повторюсь, это обрезанные конфиги.


    Примерно так. Может выглядеть сложным, но это на словах. На деле все проще пареной репы :)


    Конец лирического отступления


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


    Подняли. Туннели начали падать в разное время, но у нас прекрасно отрабатывал фейловер на уровне upstream в nginx. Но потом туннели начали падать примерно одновременно :) И снова начались 502 и 504. Uptime стал ухудшаться, поэтому мы стали прорабатывать вариант с Alibaba CEN (Cloud Enterprise Network).


    CEN


    CEN — это связность двух VPC из разных регионов внутри Alibaba Cloud, то есть можно соединить приватные сети любых регионов внутри облака между собой. И что самое главное: у данного канала есть довольно строгий SLA. Он очень стабилен как по скорости, так и по uptime. Но никогда не бывает всё так просто:


    • его ОЧЕНЬ непросто получить, если вы не китайские граждане или юрлица,
    • платить нужно за каждый мегабит пропускной способности канала.

    Получив возможность соединить Mainland China и Overseas, мы создали CEN между двумя регионами Ali: cn-shenzhen и us-east-1 (наиболее близкой точкой к us-east4). В Ali us-east-1 подняли еще одну виртуалку, чтобы был еще один hop.


    Получилось так:



    Результаты браузерных тестов ниже:



    Решение Uptime Median 75 Percentile 95 Percentile
    Cloudflare 86.6 18s 30s 60s
    IPsec 99.79 18s 21s 30s
    CEN 99.75 16s 21s 27s

    Показатели немного лучше, чем у IPSEC. Но через IPSEC потенциально можно качать со скоростью 100 мбит/c, а через CEN только со скоростью 5 мбит/c и дороже.


    Напрашивается гибрид, да? Соединить скорость IPSEC и стабильность CEN.


    Так мы и поступили, пустив трафик как через IPSEC, так и через CEN в случае падения IPSEC-туннеля. Uptime стал намного более высоким, но скорость загрузки сайта пока оставляла желать лучшего. Тогда я нарисовал все схемы, которые мы уже использовали и тестировали, и решил попробовать добавить в эту схему еще немного GCP, а именно GLB.


    GLB


    GLB — это Global Load Balancer (или Google Cloud Load Balancer). Он обладает важным для нас преимуществом: в контексте CDN у него anycast IP, что позволяет роутить трафик в ближайший к клиенту датацентр, благодаря чему трафик быстрее попадает в быструю сеть Google и меньше идет по “обычному” интернету.


    Недолго думая, мы подняли HTTP/HTTPS LB в GCP и бэкэндом поставили наши виртуалки с subfilter.


    Было несколько схем:


    • Использовать Cloudflare China Network, но в этот раз Origin’ом указать глобальный IP GLB.
    • Терминировать клиентов в cn-shenzhen, а оттуда проксировать трафик сразу в GLB.
    • Идти сразу из Китая в GLB.
    • Терминировать клиентов в cn-shenzhen, оттуда проксировать в asia-east1 через IPSEC (в us-east4 через CEN), оттуда уже идти в GLB (спокойно, внизу будет картинка и объяснение)

    Мы протестировали все эти варианты и еще несколько гибридных:


    • Cloudflare + GLB


    Эта схема не устроила нас по uptime и DNS-ошибкам. Но тест проводили до фикса бага со стороны CF, возможно, сейчас стало лучше (однако, это не исключает HTTP-таймауты).


    • Ali + GLB


    Такая схема также не устроила нас по uptime, так как GLB часто выпадал из апстрима из-за невозможности коннекта в приемлемое время или таймаута, ведь для сервера внутри Китая адрес GLB так и остается снаружи, а значит, за китайским фаерволом. Магии не случилось.


    • GLB only


    Вариант, похожий на предыдущий, только в нем не использовались серверы в самом Китае: трафик шел сразу в GLB (поменяли DNS-записи). Соответственно, результаты не устроили, так как у обычных китайских клиентов, пользующихся услугами обычных интернет-провайдеров, ситуация с прохождением фаервола намного хуже, чем у Ali Cloud.


    • Shenzhen -> (CEN/IPSEC) -> Proxy -> GLB


    Здесь мы решили использовать самое лучшее от всех решений:


    • стабильность и гарантированный SLA от CEN
    • высокую скорость от IPSEC
    • “быструю” сеть гугла и его anycast.

    Схема выглядит примерно так: трафик пользователей терминируются на виртуалке в ch-shenzhen. Там настроены апстримы nginx, часть из которых ссылается на частные IP-серверов, находящихся на другом конце IPSEC-туннеля, а часть апстримов — на частные адреса серверов на другой стороне CEN. IPSEC настраивался до региона asia-east1 в GCP (был ближайший регион к Китаю на момент создания решения. Сейчас у GCP есть также присутствие в Гонконге). CEN — до региона us-east1 в Ali Cloud.


    Далее трафик из обоих концов направлялся на anycast IP GLB, то есть в ближайшую точку присутствия гугла, и уходил по его сетям в регион us-east4 в GCP, в котором стояли подменяющие виртуалки (с subfilter в nginx).


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


    Внедрив 4-е решение из списка выше, мы достигли того, чего хотели, и чего требовал от нас бизнес на тот момент времени.


    Результаты браузерных тестов для нового решения в сравнении с предыдущими:


    Решение Uptime Median 75 Percentile 95 Percentile
    Cloudflare 86.6 18s 30s 60s
    IPsec 99.79 18s 21s 30s
    CEN 99.75 16s 21s 27s
    CEN/IPsec + GLB 99.79 13s 16s 25s

    CDN


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


    И об этом я расскажу вам в следующей, заключительной части :)


    Все части


    Часть 1
    Часть 3

    • +28
    • 10,2k
    • 3
    SEMrush
    70,34
    Компания
    Поделиться публикацией

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

      +1
      Если так важны китайские пользователи, значит надо ставить сервера в Китае. Всё остальное костыли разной степени кривизны.

      Но потом туннели начали падать. Несколько раз в сутки по несколько минут

      Балансировать трафик в нескольких туннелях параллельно и всё будет ок.
        +2
        Возможно, неточно сформулировал. Туннели из разных регионов начали падать одновременно. Такое ощущение, что кто-то нажимал рубильник в Китае.

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

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

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

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