Make. Build. Break. Reflect.
912 subscribers
116 photos
1 video
122 links
Полезные советы, всратые истории, странные шутки и заметки на полях от @kruchkov_alexandr
Download Telegram
#aws #devops #longread #longstory #msk #apigateway #cloudfront

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

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

https://telegra.ph/My-maiden-magnum-opus-08-01
🔥18👍53
#cloudfront #aws #troubleshooting #одинденьизжизни

Однажды меня попросили сделать перенос старого 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. Браузер > /welcome
2. CloudFront матчит /welcome* > origin: OLD bucket
3. S3 не находит файл /welcome (это SPA route, не файл)
4. S3 возвращает error_document = index.html
5. Браузер получает HTML старого приложения

6. HTML содержит: <script src="./static/js/main.a1b2c3d4.chunk.js">
7. Браузер резолвит ./static/... относительно /welcome
8. Браузер запрашивает /static/js/main.a1b2c3d4.chunk.js

9. 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, но их ./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