Что нам стоит CDN построить?

Привет Хабр! В этой статье мы будем строить свой CDN. Почему не воспользоваться готовыми решениями? Потому что сайт автора полностью статический, сделанный на Jekyll, с большими картинками, которые нужно отдавать максимально быстро. Сервер не должен быть кэширующим, он должен хранить сайт целиком, поддерживать HTTP/2 и Brotli, а на всех серверах должен быть установлен один и тот же сертификат.

Ещё мы сделаем это всё на IIS, работающим под Windows Server 2019 Core.

jd-8bhr2bkenojiyts1op3nxdr0.jpeg


Кратко о реализации


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

  1. Active Directory
  2. DFS
  3. IIS
  4. WinAcme
  5. RSAT


Опционально, но рекомендуется:

  1. Windows Admin Center


На узлах с сайтами нужны только службы IIS и DFS, Active Directory нужна обязательно, она требуется для DFS, которая будет синхронизировать содержимое сайта между серверами. RSAT нужен, чтобы управлять компонентами, а Windows Admin Center — чтобы править реестр. Это можно сделать и через Powershell, о чём тоже расскажу. 

Домен автора называется ***.***.wtf (пожалуйста не пугайтесь), а серверы именованы по дата-центрам и имеют имена вида cache-zur1, cahe-ru и так далее. AD развёрнута, а серверы подключены к ней. Теперь по порядку.

Выбор точек


У RUVDS 8 дата-центров — 3 в Европе и 5 в России. Мой выбор пал на Rucloud в Москве и LD8 (тот что в Лондоне). Rucloud потому, что почти ничем не отличается от М9, а через Лондон идёт трансконтинентальный кабель до США, поэтому в Европе маст хэв. Для более плотного размещения по Европе можно выбрать Швейцарию или Германию — на этом, в целом, можно остановиться.

Выбор DNS GeoIP


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

Мы можем использовать свой DNS (BIND с плагином для Maxmind) или готовое решение (Route53). Подписка на GeoIP-базы Maxmind стоит $25 в месяц, а Route53 стоит $0.50 за одну доменную зону, плюс копейки, если выйдешь за миллион запросов. К тому же, создавать ещё одну точку усиления DDoS-атак это последнее дело, поэтому мой выбор пал на Route53.

В этой статье речь идёт о CDN для Европы; если вам нужен CDN для России, то выбор в сторону своего DNS очевиден, потому что Route53 осуществляет роутинг по странам, а не по городам.
Сервисы подобного рода есть и у Майкрософт (Azure Traffic Manager).

1. Установка IIS


1.1. Установка IIS

Устанавливаем базовые компоненты IIS, компонент поддержки централизованных сертификатов, а на главном сервере дополнительно поддержку удалённого управления. Этот компонент нужен только для одного сервера — вы не сможете управлять другими серверами при включении общих конфигов, даже если удалённое управление было настроено.

f09d98c6665d1e5106436c0fd9fc258d.png

Через Powershell:  

Install-WindowsFeature Web-Server, Web-CertProvider


На главном сервере нужно дополнительно установить:  

Install-WindowsFeature Web-Mgmt-Service


1.1.1 Включаем удалённое управление

Чтобы мы могли удаленно управлять сервером IIS, нам нужно поднять службу удаленного управления IIS. На главном сервере запускаем службу:

start-service WMSVC
set-service -Name WMSVC -StartupType Automatic


И теперь включаем возможность управления как таковую через реестр.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WebManagement\Server


Проще всего управлять реестром, конечно, через Windows Admin Center. 

8fd9cb7b19b7523283885d61564eb920.png

Но это можно провернуть и через Powershell:

Set-Itemproperty -path "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WebManagement\Server" -Name "EnableRemoteManagement" -value "1"


В Windows Server 2012 и 2016 правило фаерволла не поднимается само, нужно править фаерволл. 

8c713ed356d5418549cdc602c62e3161.png

Set-NetFirewallRule -Name IIS-WebServerRole-WMSVC-In-TCP -Enabled True


Проверяем, поднялось ли:

929121aa33cdffa49494d791b583da7f.png

Test-NetConnection 8.8.8.8 -port 8172


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

1.2 Врубаем централизацию конфигов

Для установки централизованных конфигураций и сертификатов в диспетчере IIS нет кнопок, они есть только в диспетчере локального сервера, так что для Server Core делать всё придется через Powershell.

Централизация конфигов и сертификатов осуществляется через SMB. Поэтому я сделал двух бесправных пользователей (отдельно для конфигов и отдельно для сертификатов), которые имеют доступ на чтение только к своей папке и назвал их certadmin и configadmin.

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

1.2.1 Делаем общую папку

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

New-SmbShare -ReadAccess configadmin@**.**wtf -Path C:\windows\System32\Inetsrv\Config -Name sharedconfigs


А для сертификатов можем выбрать произвольный. Я помещаю их в папку с IIS.

New-SmbShare -Path C:\inetpub\centralizedcerts -Name sharedconfigs -ReadAccess configadmin 


1.2.2 Подключаем узлы к конфигам

Настройку нужно проводить только для тех серверов, которые этот конфиг будут читать. Мой главный сервер под именем cache-ru будет головой, поэтому я осуществлял настройку на двух других.
Вводим пароль пользователя, имеющего доступ к папке:

$pass = Read-Host -AsSecureString
Enable-IISSharedConfig -PhysicalPath \\cache-ru.**.**wtf\SharedConfig -UserName configadmin@**.**wtf -Password $pass -DontCopyRemoteKeys


Сразу после ввода должен открыться новый сеанс. Чтобы проверить, что всё заработало, можно открыть RSAT → Управление компьютером → Общие папки → Сеансы. Мы увидим сеансы пользователя и ip-адрес сервера, который под этим пользователем читает общую папку. На строне клиента проверяется командлетом:

Get-IISSharedConfig


Вот так это выглядело у меня:

459bc9857540abe84a63229992b7d3dd.png

1.2.2 Подключаем узлы к сертификатам

Следующую строку можно вводить только напрямую, имея прямое подключение к рабочему столу под управлением Server c GUI, на Server Core она не работает.

$pass = Read-Host -AsSecureString
Enable-IISCentralCertProvider -CertStoreLocation \\cache-ru.**.**wtf\centralizedcerts -UserName certadmin@**.**wtf -Password $pass


Если попытаетесь ввести её через Winrm, то увидите такой вывод:

85fe1bc7691f4a7c4765560e4d62a2f8.png

Чтобы сделать это удалённо, нужно править реестр. Как удобно! Залетаем в:

HKLM:\SOFTWARE\Microsoft\IIS\CentralCertProvider\


Создаём 32-битный DWORD «Enabled» с параметром »1»:

06cba1e1e6dd5af05cc7f9d0279e138f.png

Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\IIS\CentralCertProvider\ -Name Enabled -Value 1


Потом создаём строковое значение CertStoreLocation с параметром \\cache-ru.**.**wtf\centralizedcerts:

Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\IIS\CentralCertProvider\ -Name CertStoreLocation -Value \\cache-ru.**.**wtf\centralizedcerts


Чтобы проверить, всё ли в порядке, используем:

Get-IISCentralCertProvider


Аутпут должен быть таким:

c8bdd648d8d5f147c8bd28d1bad9cfc0.png

И только после этого вводим:

$pass = Read-Host -AsSecureString
Set-IISCentralCertProvider -UserName certadmin@**.**wtf -Password $pass


Ещё раз проверяем:

Get-IISCentralCertProvider


Всё из коробки, что называется. В отличие от общих конфигов, централизованные сертификаты не создают активную SMB-сессию.

1.2.3 Brotli

На каждый из серверов скачиваем и запускаем файл IIS Compression

Start-Process .\iiscompression_amd64.msi -ArgumentList /quiet


Мы уже расшарили конфиги, поэтому конфигурировать что-либо нужно только в одном месте. С помощью диспетчера IIS переходим в редактор конфигураций на главном сервере.

4898744dbe1eea52b6bfe3c92ea5a1ad.png

system.webServer/httpCompression


И устанавливаем StaticCompressionLevel на 11 для Brotli и на 9 для Gzip, это максимум, что они умеют.

1.2.4 MIME и заголовки

Из коробки IIS не передаёт заголовок о кодировке, из-за чего кириллица превращается в руны. Также в MIME отсутствует запись о формате webp, которую нужно добавить.

1. Идём в диспетчер → MIME. Находим .HTML и модифицируем его тип на: text/html; charset=UTF-8

e4cbef017ae044d8720535ff9bdea63a.png

2. Не забываем про Webp — этот MIME нужно добавить вручную.

07ab62aecf03ee9b4a32ecd4044d7a42.png

2. Выпускаем сертификат


С помощью Winacme. Сначала, нужно забиндить домены к сайту — это можно сделать через диспетчер IIS. При построении CDN не выбирайте верификацию по хосту, т.к. сразу начинаются проблемы с подтверждением владения доменом, наш выбор — верификация по TXT-записи. Придётся вручную вбивать путь к папке с cертификатами, так же вручную копировать запись из челленджа. А теперь, наберите воздуха в грудь.

Чтобы не печатать всё вручную, включаем RDP на Server Core следующей командой:

cscript C:\Windows\System32\Scregedit.wsf /ar 0


Вот так выглядит RDP на Server Core:

8562ea478872a08964c125a8b997eae6.png

После выполнения челленджа сертификаты падают в папку, которая была указана в Winacme. Winacme ставит задачу в планировщик на обновление сертификата. Установили и забыли.

f208e3d33f61e407a19e1ff53e14ecde.png

Ну, а как закончили, можем выключить RDP — не зря ставили Server Core, столько нюансов обнаружили.

cscript C:\Windows\System32\Scregedit.wsf /ar 1


3. Биндим сертификат к узлам


Теперь нужно настроить две другие ноды, чтобы наконец заработал HTTPS. В это время на сервере, где хранятся сертификаты, HTTPS уже работает. Чтобы и остальные начали работать по HTTPS, ну, вообщем, это делается через netsh. 

Как уже мы привыкли, подключаемся к Windows Server Core по RDP чтобы запустить эту оснастку. Не смотрите на сайт в майкрософт, особенно в это руководство. Шаги в нём описаны неправильно.

С помощью netsh http show sslcert, на мастер-ноде нужно получить appid, который вводим так, как написано ниже:

netsh http add sslcert ccs=443 appid= '{4dc3e181-e14b-4a21-b022-59fc669b0914}'


Значение appid должно быть в кавычках, иначе не сработает.

4. Установка DFS


Не синхронизируйте сертификаты и конфиги через DFS, специализированные инструменты придуманы не просто так. Я пытался и вышло плохо, по какой-то неясной причине конфигурации на серверах, которые конфиг брали, просто перестали читать изменения, хоть и по первому времени это всё работало. Причину сбоя я не выяснял и решил сделать по-другому.

Дальнейшая настройка производится исключительно через RSAT, командлеты которые указаны в документации, работают только под управлением Windows Server с GUI. Развернуть репликацию DFS, используя исключительно Server Core, невозможно. Нужен либо ещё один сервер с GUI, либо компьютер на Windows 10 Pro с установленным RSAT, присоединённым к домену.

2.1. Устанавливаем

На каждый из серверов нужно установить DFS Replication:

ebf999b179089dd87a346406bfe6a09d.png

Install-WindowsFeature FS-DFS-Replication, FS-DFS-Namespace


2.2. Делаем новую группу репликации

53b3e29d6aee1c0e9f9a6b4e8f5ea06e.png

Сперва нужно пошло назвать нашу группу репликации.

14d7cea23a3bd08b1aec745ed40c0fb3.png

Выбирайте топологию на свой вкус. Лично мне удобнее будет добавлять контент в одно место, поэтому я выбираю звезду.

2fba17e99d83b3bec4940c5517c2a690.png

7a26e4508dfe041bc25c5cf008120ec2.png

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

d3629812afb96964a66db8a1c7682513.png

В качестве папки для репликации я выбрал дефолтную папку с сайтами:  

C:\inetpub\wwwroot


Эта папка — путь к каталогу конкретно этого, первого сервера. Реплицировать содержимое этой папки можно куда угодно, независимо от пути.

d10e94f5e3e48d88ddcc3a5cb97ff5a4.png

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

cccae9649e5b0145fb38a6cd59d73490.png

Закончили.

5. Тестируем производительность


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

d2612c04abedc5d8eb796cbeeaaf31f7.png

Заходим в Devtools и наблюдаем водопад. В среднем по больнице, CDN сократит около 200 миллисекунд до полной загрузки сайта. Картинка, самый большой объект на странице, загружается как только попадает во вьюпорт, поэтому обратите внимание на синюю вертикальную черту. CDN ускорил чистый HTML + стили и скрипты на 10–15 миллисекунд, а картинка загрузилась на 220 миллисекунд быстрее, это очень значительная разница.

Москва — Москва:
12b5e58a9665f6901f073c0eba3f2678.png


Лондон — Москва:
03e435b93de7b098fbd98afdacc0c8f4.png


Цюрих — Москва:
1f2474cfba94d72d5482cb9a0ba0c679.png


Также я замерял скорость Jekyll«a на локальном хосте:

514976285849bbf746909644173302ba.png

Переходим к лайтхаусу и видим странные результаты:

Москва — Москва:
f2b516abbaeb5ea7ec29c39585719d38.png


Лондон — Москва:
9ab9d838cd9c67792381c0741ef81a27.png


Цюрих — Москва:
12e50000b52c228beb3f08367fbbf00d.png


Localhost — Jekyll:
3eb449599c0eee7698301470b48ea67b.png


В этой синтетике сервер, который за тридевять земель обходит локального хоста. Но почему?
Давайте ради интереса избавимся от Google Fonts и перенесём их на наши серверы.

Москва — Москва:
4570827cb685795120f645da992ebcf8.png


Лондон — Москва:
f90fbd2ad39d80e6b246faf348fee577.png


Цюрих — Москва:
d452f0530f2d1d914aba53649cff763d.png


Localhost — Jekyll:
bc26a1f275f6d3626de02d96b436c186.png


Результаты между дата-центрами выровнялись. Но это всё равно ничего не объясняет. Я переделывал тесты несколько раз, результаты абсолютно железные и воспроизводимые в любое время.

Теперь воспользуемся VPS с двумя ядрами и 4 гигабайтами ОЗУ, расположенную во Франкфурте и посмотрим, что скажет она. Водопады:

Москва — Франкфурт:
025d40dd7962e7e312a6978cff91cabd.png


Лондон — Франкфурт:
961e9facf3d0a4654654f3541c7093a6.png


Цюрих — Франкфурт:
d5a81e399de00ca9f3a451d09379f6cd.png


Тут, при правильно выбранной точке экономия времени лишь на стилях и HTML составила 200 миллисекунд. То есть в случае с клиентом из Франкфурта сайт просто будет быстрее на 200 миллисекунд. Это также отражается и в лайтхаусе. В подобных случаях CDN ускорит не только интернет-магазин, но и лёгкий блог.

Москва — Франкфурт:
e4de88785e56ed3102cef5ade05f8462.png


Лондон — Франкфурт:
042b1c0716f7e44b2f2dfc0b3955a68f.png


Цюрих — Франкфурт:
db1ae1de5ebfd5be8c3af5e6822a4376.png


А теперь включим Google Fonts и снова посмотрим на лайтхаус.

Москва — Франкфурт:
87dae6281e2510fa35ee4aecd45079f0.png


Лондон — Франкфурт:
025ec2d7d6982b39261551f927167ad2.png


Цюрих — Франкфурт:
802f0c073e6fa0f445dda60d7823da00.png


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

Выводы


  • CDN для вывода всей статики необходим, особенно если речь идёт о тяжёлых картинках или длинных скриптах. 
  • Использовать Google Fonts можно и нужно, в тяжёлых сценариях это действительно ускоряет сайт.
  • Главный сервер, если решите делать всё это на Windows, желательно должен иметь GUI.
  • Пользователей, которые будут брать конфиги с главного сервера, лучше всего делать локальными — в случае выхода из строя контроллера домена, ноды могут потерять связь с конфигами и сертификатами, а Application Pool будет остановлен, т.к. не сможет прочитать конфиг.

itt53pns2iucwylb3bwn1fmmtnu.png

© Habrahabr.ru