Заливаємо вебаплікацію на CDN від AWS

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

Вітаю, я Сенчук Роман, працюю на посаді Solution Architect Associate в Vodworks. Цей тайтл не часто зустрічається в українських компаніях, є проміжним етапом між Tech Lead та Solution Architect. В основному маю справу з AWS та GCP.

У цій статті хочу коротко розказати про те, як захостити свою вебаплікацію на CDN від Amazon. Це може бути цікаво всім, хто хоче покращити доступ до свого ресурсу, а саме прискорити завантаження статичного контенту користувачами.

Часто оптимальним рішенням є використання Мережі Розповсюдження Контенту (CDN). Статичним контентом тут може бути як збілджена JavaScript Аплікація (React чи Angular), так і зображення чи статичний HTML. У цій статті оперуватимемо AWS S3 (для збереження та хостингу), CloudFront (як CDN) та функціоналом DNS-провайдера (може бути будь-який), у нашому випадку — це CloudFlare. Також використовуватимемо TLS/SSL сертифікат від Cloudflare для того, щоб ресурс працював на HTTPS (з підтримкою Cloudflare «Full Strict» режиму).

Завдання здається простим і є багато ресурсів з висвітлення подібного, проте, як виявилось, є багато підводних каменів, і аналогічний контент часто застарілий.

Тут не будемо розглядати створення та білд JS аплікації, створення S3 бакета (або ж «відра» українською), залиття контенту в бакет, чи реєстрації DNS-домену.

HTTP на S3

Одже, створюємо S3 бакет та заливаємо збілджений код туди, в корінь. Проте тут є нюанс — обмеження від Amazon: бакет повинен мати ім’я таке ж, як домен (піддомен). Наприклад,
якщо планується розмістити білд на домені my.react-app.com, то й назва бакету повинна бути my.react-app.com.

Далі у властивостях бакету (вкладка Properties) потрібно ввімкнути Static website hosting. Нижче, в полі Index Document, потрібно вказати index файл (index.html зазвичай).

Зберігаємо налаштування та переходимо до вкладки доступів (Permissions).

Тут нам потрібно вимкнути блокування доступів, якщо воно активне (Block public access).

В Bucket Policy (Політика доступу) нам потрібно надати доступ для CloudFront. Щоб спростити задачу, надамо доступ всім сервісам AWS (потім це можна переналаштувати).

В полі для JSON потрібно скопіювати наступне:

 {
    "Version": "2012-10-17",
    "Id": "Policy1638279142165",
    "Statement": [
        {
            "Sid": "Statement1",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::my.react-app.com/*"
        }
    ]
}

де в рядку «Resource»: «arn:aws:s3:::my.react-app.com/*»
«my.react-app.com» потрібно замінити ім’ям цього бакета.

Зберігаємо налаштування та намагаємось доступитись до аплікації через HTTP. Для цього на вкладці властивостей бакету (Properties) в полі Static website hosting переходимо по лінку, вказаному під пунктом «Bucket website endpoint».

На цьому етапі ми повинні мати доступ до нашого ресурсу під згенерованим доменом через HTTP. Якщо з цим виникли проблеми, то варто спробувати пройтись по попередніх налаштуваннях ще раз, перш ніж рухатись далі.

SSL/TLS сертифікат

Щоб ресурс працював через HTTPS, потрібно використати сертифікат, згенерований для потрібно домену. Якщо на AWS у вас вже є сертифікат (наприклад, якщо домен на Route 53), цей крок можна пропустити.

Для менеджменту сертифікатів в AWS є сервіс Certificate Manager (ACM). Тож заходимо туди та обираємо опцію запиту сертифіката (Request certificate). Далі обираємо опцію публічного сертифікату (Request a public certificate).

Тут є перший нюанс: для того, щоб сертифікат можна було використовувати в Cloudfront, його потрібно додавати саме в зоні доступності «N.Virginia» (us-east-1), що зовсім не інтуїтивно.

Деталі — тут.

Там заповнюємо назву домену:

Та спосіб валідації:

Обираємо DNS-валідацію, адже саме цей спосіб є рекомендованим. Натискаємо Request, щоб створити запит на отримання сертифікату.

Після цього сертифікат з’явиться в списку сертифікатів зі статусом «Очікує валідації» (Pending validation). Щоб валідувати його, потрібно створити DNS-запис згідно згенерованих значень:

Ми підтвердили приналежність поточного ресурсу до вказаного домену (а значить і сертифікату).

Тож додаємо відповідний CNAME запис до DNS (в нашому випадку в Cloudflare):

Через певний час наш сертифікат буде валідованим та отримає статус «Issued».

Cloudfront

Cloudfront — це CDN сервіс від Amazon, який за допомогою глобально розподілених проксі-серверів кешує контент, у нас — складові аплікації. Тож нам потрібно налаштувати дистрибутив для розподілення з джерелом даних у попередньо створене S3 відро (bucket).

В Distributions обираємо Create та заповнюємо Origin domain.

Тут ще один зовсім не інтуїтивний момент: коли ми вводитимемо назву бакета, нам підтягнеться наш S3 ресурс. Проте це не те, що нам потрібно.
В це поле потрібно вставити URL, за яким ми раніше через HTTP мали доступ до нашого бакету. Щоб його отримати, йдемо до «Властивостей» нашого бакету (Bucket Properties) та копіюємо адресу з поля website endpoint. Вставляємо його без http:// та без слешу в кінці.

До прикладу, в нашому випадку, в полі Origin domain при створенні дистрибутива у нас буде підказка:

«my.react-app.com.s3.amazonaws.com» (закінчується «s3.amazonaws.com» )

а потрібно:
«my.react-app.com.s3-website-eu-west-1.amazonaws.com»

Далі шукаємо поле Alternate domain name. Тут потрібно вказати домен (назву бакету), наприклад my.react-app.com.

Та обрати створений вище сертифікат.

Всі інші поля залишаємо без змін, та створюємо дистрибутив.

Спочатку дистрибутив отримає статус «deploying», і коли стане готовим до використання, статус зміниться на «Enabled».

На цьому етапі наш ресурс повинен бути доступний через потрібний HTTPS на CDN Amazon. Щоб переконатись в цьому, потрібно перейти на URL, вказаний в деталях новоствореного дистрибутиву:

Фінальний штрих

Тепер, щоб дана аплікація працювала під нашим доменом, потрібно створити відповідний DNS запис, в якому ми впишемо, що за контентом для вказаного домену потрібно звернутись до налаштованого вище дистрибутиву Cloudfront (Distribution domain name). Оскільки дистрибутив доступний по URL, використаємо той же CNAME запис.

Щоб DNS запис вступив в дію, потрібен певний час.

DNS провайдер Cloudflare

Якщо все пройшло добре, та всі інші DNS записи відповідають вимогам end-to-end HTTPS шифрування то можна ввімкнути режим «Full (strict)».

Index файл не знайдено

Якщо на якомусь етапі замість аплікації відкривається XML з переліком файлів у відрі, то ймовірно, що cloudfront не знає, який індекс файл використовувати. Щоб виправити це, можна налаштувавши шлях до індекс файлу (Origin path) в Cloudfront дистрибутиві.

👍ПодобаєтьсяСподобалось3
До обраногоВ обраному5
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

цікавіше було би взнати причину вибору CDN Cloudflare а не CloudFront (ціна, latency(з яких регіонів більшість клієнтів), ttfb, ітд.
2 Author:і здається по дорозі Cloudflare ssl сертифікат перетворився на AWS ACM сертифікат? ну і як на мене Route53 з його alias рекордами які можуть робити хелсчеки на декілька ресурсів і робити по них dns routing/failover, latency based, weighted routing це більш гнучке рішення.

При такому сетапі хостинг особистого статичного сайтику на пару мегабайт і з невеликою відвідуваністю виходить безкоштовним. Cloudflare тут, певно, чисто як безкоштовний DNS, тому що Route53 коштує $0.5 за зону в місяць, а SSL можна і та і там мати безкоштовно. Фейловера, хелсчеків і решти, що Ви описали — тут не треба, бо ходимо напряму на CloudFront. А по сертифікатах то трохи каша описана, бо у сетапі з CloudFlare використовується пряма адреса CloudFront дистрибʼюшена, і у випадку конфігурації CloudFlare в режимі проксі, саме те імʼя і його сертифікат будуть використовуватися, не my.react-app.com. Сенс використання АСМ і кастомного доменного імені на CloudFront зникає.

так, саме як

як безкоштовний DNS

і так, є каша ... рудимент по початковому налаштуванню на Route 53.
Виправлюсь, дякую

1. тут про CDN Cloudflare не йдеться. Використав його тільки як приклад DNS провайдера.
2. насправді, так Route 53 має свої переваги, проте можна використовувати й інші DNS провайдери, особливо, якщо є така опція безкоштовно.

Поспішаю виправити трохи автора і вказати на правильніший шлях «звʼязування» бакета і CloudFront. Не потрібно на бакеті вмикати статичний хостинг і робити його публічним. І назва його може бути довільною, головне, щоб набір символів відповідав формату FQDN.
Кроки наступні:
1. Створюємо бакет, називаємо будь-як, головне символи: my-website-123
2. Створюємо CloudFront distribution, в налаштуваннях Origin’а вибираємо свій бакет, і вказуємо, щоб використовувася origin access identity (OAI) при роботі з бакетом. Це дозволить не робити його публічним, не вмикати на бакеті хостинг. Єдине, це так само треба буде додати bucket policy, але в ньому в секції Principal вказавти оцей OAI, який згенерувався. Можна вибрати пункт «Yes, update the bucket policy» і CloudFront сам оновить bucket policy, він виглядатиме тоді якось так:


{
«Version»: «2008-10-17»,
«Id»: «PolicyForCloudFrontPrivateContent»,
«Statement»: [
{
«Sid»: «1»,
«Effect»: «Allow»,
«Principal»: {
«AWS»: «arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity XXXXXXXXXXXXXX»
},
«Action»: «s3:GetObject»,
«Resource»: «arn:aws:s3:::my-website-123/*»
}
]
}

Ще не забути вказати в CloudFront опцію Default root object, поставити index.html, чи що ви там хочете.

Все. Трохи простіше і правильніше.

І ще маю доповнення стосновно DNS. Якщо використовувати Route53, то можна створити аліас на CloudFront URL і тоді my.react-app.com буде А рекордом. У випадку з CloudFlare рішення — тільки CNAME на URL дистрибʼюшена.

UPD: Origin path дозволяє вказувати на якусь конкретну папку в бакеті. А для вирішення описаної проблеми все ж треба Default root object прописувати.

А ещё версионирование бакета не включили.
Обновить cdn придётся через удаление distribution.

Версіонування то вже не про мінімальний сетап. Можна в бакеті розкидувати по різних папках, називати їх, скажімо, по SHA і оновлювати їх в Origin path. Потім, якщо швикдо треба оновити все — робити інвалідацію кеша.

Ну просто это быстрее всего.
в 1 клик и все с коробки
Я не правильно написал выше. Без инвалидации всего кэша

Почему же удалить? Можно на объекты в бакете навесить Cacha-Control: maxage, и CloudFront сам запросит обновление по истечению срока. Можно, как уже сказали? Использовать разные base path, которые включают в себя версию или хеш билда.
На самый худой конец, можно использовать Invalidations.

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

Он даст с коробки возможность апдейта контента без доп телодвижений.

До тех пор, пока время жизни объекта на Edge Location не подойдет к концу, CloudFront попросту даже не «придет» в бакет за новой версией. Единственным способом заставить его это сделать для того же URL — invalidation request.

освежу знания, чтобы не разбрасываться попусту догадками.
потому что всегда считал, что включение версионирования дает мета тэг версии, который считается cloudfront и соответственно инвалидирует только 1 объект в кэше или тянет последний.
x-amz-version-id как-то так

інвалідація CDN нажаль за поза межами скоупу даної статті. Проте це частина наступної з використанням AWS CodePipeline.

Чудовий коментар.
Спосіб з origin access identity дійсно кращий та загалом правильніший, дякую.
Проте, на мій погляд, менш наглядний, адже результат можна буде побачити тільки після 2-го кроку (налашутвання Cloudfront). Зважайте, що скоріше за все читач цієї статті налаштовуватиме CDN вперше, і можливість побачити результат одразу після налаштування бакету важлива.
Проте я б волів додати вашу примітку до матеріалу, якщо ви не проти.

Стосовно Route 53, його звісно теж можна використовувати і саме в ньому можна А рекорд додавати, має сенс зробити примітку

Стосовно «UPD», це ви про «Index файл не знайдено», так? гляну

Так, будь ласка, додавайте.
«UPD» саме про index файл.

До речі, з дефолтним index.html в CloudFront все трохи тяжко, бо це працює лише для обʼєктів в корені my.react-app.com/ , наприклад. Щоб тягнути дефолтний індекс з субфолдерів my.react-app.com/blog , то треба вже використати CloudFront Functions або [email protected] для перетворення запитів на льоту. CloudFront Functions дешевші.

Зважайте, що скоріше за все читач цієї статті налаштовуватиме CDN вперше, і можливість побачити результат одразу після налаштування бакету важлива.

В такому разі можна подати цю секцію у кілька кроків:

  1. Налаштовуємо бакет та перевіряємо, чи все з ним ок
  2. Налаштовуємо CDN та вимикаємо публічний доступ до бакету

Підписатись на коментарі