#aws #devops #longread #longstory #msk #apigateway #cloudfront
Суббота, отличная погода и великолепное настроение - идеальный момент для меня, чтобы написать новую историю.
А читателям можно устроиться поудобнее с бокалом любимого напитка и неторопливо погрузиться в лонгрид, полный разных идей, технологий и подходов.
https://telegra.ph/My-maiden-magnum-opus-08-01
Суббота, отличная погода и великолепное настроение - идеальный момент для меня, чтобы написать новую историю.
А читателям можно устроиться поудобнее с бокалом любимого напитка и неторопливо погрузиться в лонгрид, полный разных идей, технологий и подходов.
https://telegra.ph/My-maiden-magnum-opus-08-01
Telegraph
Webhook Buffer
Alexandr Kruchkov Несколько лет назад мне предоставилась первая возможность разработки своей первой архитектуры с нуля. Я только-только устроился на новую работу, начал разбираться с AWS, Azure, всей инфраструктуре и прочим-прочим. Это был мой первый практический…
🔥18👍5❤3
#cloudfront #aws #troubleshooting #одинденьизжизни
Однажды меня попросили сделать перенос старого SPA (Single Page Application)-приложения на новый S3 bucket в CloudFront.
Но не просто перенос, а хитрый: часть путей должна идти на новое приложение, а часть - оставаться на старом.
Legacy страницы типа /welcome, /login-start, /login-end пока не переписаны в новом коде, их надо сохранить.
А всё остальное - на свежий
По сути частичная миграция на новое приложение, не ломая старое.
Казалось бы, что может пойти не так?😁
Задача звучит просто:
-
-
-
Открываю Terraform, добавляю новый origin для app-v2 bucket.
Меняю
Добавляю
Terraform plan - всё как задумано.
Apply. Жду. Готово.
Иду проверять.
- https://stage-app.example.com/ - работает, новое приложение грузится. ✅
- https://stage-app.example.com/?appId=xxx - работает, редиректит. ✅
- https://stage-app.example.com/welcome - ...
Чо.🤡
Первая мысль - кэш CloudFront. Делаю invalidation. Жду. Проверяю.
Та же херня.
Вторая мысль - может origin неправильный?
Проверяю напрямую S3 bucket:
Bucket работает. index.html есть. 4755 байт. Всё ок.
Третья мысль - может
В CloudFront есть глобальный обработчик 404 ошибок, который возвращает
Думаю: "А, наверное S3 возвращает 404, а CloudFront берёт index.html с дефолтного origin!"
Убираю
Та же херня. Даже хуже - теперь вместо "Server Error" просто "Not found".
Возвращаю
Ладно, поехали курлить по-взрослому.
Стоп. 200? Не 404? CloudFront отдаёт 200 и HTML?
Смотрю body:
Да, это HTML старого приложения.
Так почему "Server Error" в браузере?
Если HTML грузится, значит проблема в JavaScript.
Приложение падает после загрузки.
Открываю DevTools > Network.
И тут я вижу ЭТО:
JS файлы возвращают HTML вместо JavaScript.
Браузер пытается выполнить HTML как JavaScript.
Приложение падает с "Server Error".
Сука.😁
Проверяю напрямую S3:
S3 отдаёт правильно! 1MB JavaScript!
А через CloudFront:
CloudFront отдаёт HTML. И
Теперь понятно что происходит.
Сажусь рисовать на планшете путь запроса (а я всегда рисую).
Проблема:
1. Браузер >
2. CloudFront матчит
3. S3 не находит файл
4. S3 возвращает
5. Браузер получает HTML старого приложения ✅
6. HTML содержит:
7. Браузер резолвит
8. Браузер запрашивает
9. CloudFront матчит
10. В app-v2 bucket НЕТ файла main.a1b2c3d4.chunk.js!
11. S3 возвращает 404
12. CloudFront custom_error_response > /index.html
13. Браузер получает index.html (HTML) вместо JavaScript
14. JavaScript парсер: "че за херня, это не JS"
15. Приложение: "Server Error"🐒
Однажды меня попросили сделать перенос старого SPA (Single Page Application)-приложения на новый S3 bucket в CloudFront.
Но не просто перенос, а хитрый: часть путей должна идти на новое приложение, а часть - оставаться на старом.
Legacy страницы типа /welcome, /login-start, /login-end пока не переписаны в новом коде, их надо сохранить.
А всё остальное - на свежий
app-v2.По сути частичная миграция на новое приложение, не ломая старое.
Казалось бы, что может пойти не так?
Задача звучит просто:
-
/* > новый bucket (app-v2)-
/welcome*, /login-start*, /login-end*, /help-bot* > старый bucket-
/assets/*, /sdk.js > старый bucketОткрываю Terraform, добавляю новый origin для app-v2 bucket.
Меняю
default_cache_behavior на новый origin.Добавляю
ordered_cache_behavior для legacy путей на старый bucket.Terraform plan - всё как задумано.
Apply. Жду. Готово.
Иду проверять.
- https://stage-app.example.com/ - работает, новое приложение грузится. ✅
- https://stage-app.example.com/?appId=xxx - работает, редиректит. ✅
- https://stage-app.example.com/welcome - ...
"Server Error. An unexpected error happened."
Чо.
Первая мысль - кэш CloudFront. Делаю invalidation. Жду. Проверяю.
Та же херня.
Вторая мысль - может origin неправильный?
Проверяю напрямую S3 bucket:
curl -I "http://stage-app.example.com.s3-website-us-east-1.amazonaws.com/index.html"
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 4755
Bucket работает. index.html есть. 4755 байт. Всё ок.
Третья мысль - может
custom_error_response мешает?В CloudFront есть глобальный обработчик 404 ошибок, который возвращает
/index.html.Думаю: "А, наверное S3 возвращает 404, а CloudFront берёт index.html с дефолтного origin!"
Убираю
custom_error_response. Apply. Проверяю.Та же херня. Даже хуже - теперь вместо "Server Error" просто "Not found".
Возвращаю
custom_error_response. Сижу, чешу репу.Ладно, поехали курлить по-взрослому.
curl -sI "https://stage-app.example.com/welcome"
HTTP/2 200
content-type: text/html
x-cache: Hit from cloudfront
Стоп. 200? Не 404? CloudFront отдаёт 200 и HTML?
Смотрю body:
curl -s "https://stage-app.example.com/welcome" | head -20
Да, это HTML старого приложения.
webpackJsonstatham@app/web. Всё верно.Так почему "Server Error" в браузере?
Если HTML грузится, значит проблема в JavaScript.
Приложение падает после загрузки.
Открываю DevTools > Network.
И тут я вижу ЭТО:
/static/js/main.a1b2c3d4.chunk.js → content-type: text/html ❌
/static/css/main.e5f6g7h8.chunk.css → content-type: text/html ❌
JS файлы возвращают HTML вместо JavaScript.
Браузер пытается выполнить HTML как JavaScript.
Приложение падает с "Server Error".
Сука.
Проверяю напрямую S3:
curl -sI "http://stage-app.example.com.s3-website-us-east-1.amazonaws.com/static/js/main.a1b2c3d4.chunk.js"
HTTP/1.1 200 OK
Content-Type: text/javascript
Content-Length: 1080675
S3 отдаёт правильно! 1MB JavaScript!
А через CloudFront:
curl -sI "https://stage-app.example.com/static/js/main.a1b2c3d4.chunk.js"
HTTP/2 200
content-type: text/html
x-cache: Error from cloudfront
CloudFront отдаёт HTML. И
x-cache: Error from cloudfront. Красота.Теперь понятно что происходит.
Сажусь рисовать на планшете путь запроса (а я всегда рисую).
Проблема:
1. Браузер >
/welcome2. CloudFront матчит
/welcome* > origin: OLD bucket3. S3 не находит файл
/welcome (это SPA route, не файл)4. S3 возвращает
error_document = index.html5. Браузер получает HTML старого приложения ✅
6. HTML содержит:
<script src="./static/js/main.a1b2c3d4.chunk.js">7. Браузер резолвит
./static/... относительно /welcome8. Браузер запрашивает
/static/js/main.a1b2c3d4.chunk.js9. CloudFront матчит
/* (default) > origin: app-v2 bucket ❌10. В app-v2 bucket НЕТ файла main.a1b2c3d4.chunk.js!
11. S3 возвращает 404
12. CloudFront custom_error_response > /index.html
13. Браузер получает index.html (HTML) вместо JavaScript
14. JavaScript парсер: "че за херня, это не JS"
15. Приложение: "Server Error"
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
#cloudfront #aws #troubleshooting #одинденьизжизни
Вот оно.
Два разных SPA с разными static assets в одном CloudFront distribution.
Legacy paths идут на старый bucket, но их
А там этих файлов нет.
Классика жанра: симптомы в одном месте, причина в другом.
Ошибка "Server Error" от JavaScript, а проблема в CloudFront routing.
Логи чистые, метрики зелёные, status code 200 - всё отлично, только ничего не работает.
Для визуалов:
Ну или вот так, что не работало (красиво только на ПК):
Мораль:
Нельзя без дополнительной логики безопасно смешать два SPA с разными static assets в одном CloudFront distribution, если они используют пересекающиеся пути (/static/*) и простой path-based routing.
Относительные пути (./static/...) превращаются в абсолютные (/static/...) и летят на default origin.
И не всегда виноват девопс, что криво пилит редирект или бихейвиор.😀
Решения:
- отдельные CloudFront distributions для старого и нового приложения
- Cloudflare Workers / Lambda@Edge для умного роутинга
- добавить /static/* behavior на старый bucket (но тогда сломается новое приложение)
- не переключать default, тестировать новое приложение на отдельном домене
Я выбрал вариант 4 (вроде, уже не помню) и пошёл пить кофе.
А RFC переписали, убрав требование "переключить трафик через один CloudFront".
Иногда лучшее решение - не решать задачу в лоб.
Вот оно.
Два разных SPA с разными static assets в одном CloudFront distribution.
Legacy paths идут на старый bucket, но их
./static/* резолвится как /static/* и летит на default origin - новый bucket.А там этих файлов нет.
Классика жанра: симптомы в одном месте, причина в другом.
Ошибка "Server Error" от JavaScript, а проблема в CloudFront routing.
Логи чистые, метрики зелёные, status code 200 - всё отлично, только ничего не работает.
Для визуалов:
ДО (работало):
══════════════════════════════════════════
CloudFront
│
▼
┌─────────┐
│ OLD S3 │ ← всё идёт сюда
│ bucket │
└─────────┘
ПОСЛЕ (сломалось):
══════════════════════════════════════════
CloudFront
│
┌───────────┴───────────┐
▼ ▼
┌─────────┐ ┌─────────┐
│ OLD S3 │ │ NEW S3 │ ← default
│ bucket │ │ bucket │
└─────────┘ └─────────┘
/welcome* /* (всё остальное)
/login-* включая /static/* ❌
Ну или вот так, что не работало (красиво только на ПК):
Browser CloudFront S3
| | |
| GET /welcome | |
|------------- req ---------->| |
| | /welcome* --> OLD bucket |
| |------------- req ---------->| OLD bucket
| | | (app-v1)
| |<------------ res -----------| index.html
|<----------- res ------------| HTML + <script> |
| | |
| GET /static/js/main.js | |
|------------- req ---------->| |
| | /* (default) --> NEW |
| |------------- req ---------->| NEW bucket
| | | (app-v2)
| | |
| | main.js NOT FOUND! |
| |<------------ 404 -----------|
| | |
| | custom_error_response |
| | 404 --> /index.html |
| |------------- req ---------->| NEW bucket
| |<------------ res -----------| index.html
|<----------- res ------------| HTML (not JS!) WRONG! | (app-v2!)
| | |
| "Server Error" | |
| JS parser: WTF?! | |
Мораль:
Нельзя без дополнительной логики безопасно смешать два SPA с разными static assets в одном CloudFront distribution, если они используют пересекающиеся пути (/static/*) и простой path-based routing.
Относительные пути (./static/...) превращаются в абсолютные (/static/...) и летят на default origin.
И не всегда виноват девопс, что криво пилит редирект или бихейвиор.
Решения:
- отдельные CloudFront distributions для старого и нового приложения
- Cloudflare Workers / Lambda@Edge для умного роутинга
- добавить /static/* behavior на старый bucket (но тогда сломается новое приложение)
- не переключать default, тестировать новое приложение на отдельном домене
Я выбрал вариант 4 (вроде, уже не помню) и пошёл пить кофе.
А RFC переписали, убрав требование "переключить трафик через один CloudFront".
Иногда лучшее решение - не решать задачу в лоб.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍2