Изображения играют важную роль в продаже авторских туров. Когда стартап в сфере туризма, маркетплейс авторских туров YouTravel.me начал обрабатывать 2,5 млн запросов на картинки и отдавать 50 GB в сутки, команда разработки задумалась, как хранить изображения, чтобы они не теряли качество, и при этом не тратить космические бюджеты. CTO  проекта Иван Михеев рассказывает, как сделали свой ресайзер для картинок и в 2 раза сократили косты. YouTravel.me — международный маркетплейс авторских туров от независимых гидов. Путешествия — это всегда про эмоции и впечатления, поэтому в таком продукте ключевую роль играют не рациональные аргументы, а картинки, которые ярче слов описывают опыт, который ты в итоге получишь в поездке. Нам нужно было хранить все изображения и оперативно отдавать их клиенту, чтобы скорость получения и качество картинки не падали. Плюс надо было адаптировать их под разное количество устройств и витрин. Учитывая, что мы работаем на международном рынке, мы должны это делать для пользователей по всему миру. Мы задумались о новом способе для хранения и передачи изображений, потому что: Стало приходить 50GB трафика в сутки. Увеличилась нагрузка до 2,5 млн запросов. Мы тратили $400 в месяц на стороннее SaaS решение, которое позволяло нам изменять картинки на лету и распределять их через content delivery network (CDN). Такое решение обходилось нам довольно дорого, поэтому мы задумались, есть ли варианты дешевле.  Сравнили Cloudinary, Imagekit и Uploadcare, и увидели, что некоторые из сервисов строят свой продукт на базе AWS или его аналогов. Чтобы сравнение получилось более точным, мы взяли 1GB как общую единицу измерения. В таблице мы указали, сколько стоит гигабайт пропускной способности и общую сумму за месяц за 1 500GB  с учётом доплат. Мы решили разобраться, как работают CDN платформы. Схема была довольно простой — хранилище и Lambda функции, которые сжимают, изменяют размер и передают изображения пользователю.  Сравнив все варианты, мы решили детальнее изучить Amazon CloudFront, ведь по сути нам из всех сервисов нужен было только CDN и их Image API, которое позволяет конвертировать изображения на лету. Чтобы не платить комиссию сторонним платформам, мы решили сами сделать такое решение, которое бы позволило нам самостоятельно и быстро обрабатывать картинки. Кроме того, у нас был грант от Amazon на $5000, который нам позволил провести этот эксперимент почти бесплатно. Задача оказалась проще и быстрее, чем мы сами думали, ниже делимся кусочком кода. План действий был таким: Создать  S3 storage для измененных картинок. Создать CDN Distribution. Написать Lambda функцию. Сначала нам нужно создать хранилище S3. Для этого в главном меню консоли переходим к S3 сервису и нажимаем на кнопку “Create bucket”. 2. На следующем экране указываем имя(1) этого хранилища и регион(2), в котором оно будет развернуто.  3. Если все сделано правильно, в списке появится созданное нами хранилище. 4. Дальше для тестирования загружаем в него любое случайное изображение. Оно нам пригодится на следующих этапах. Первым делом нужно создать Origin Access Identity(OAI), чтобы мы могли выдать созданной точке CDN доступ к S3 хранилищу. Для этого нужно перейти к сервису CloudFront и выбрать слева в меню(1) Origin access identity.  Далее на экране настроек нажать на кнопку “Create Origin Access Identity”.  2. После этого нажимаем Create Distribution, создаём точку распределения контента. 3. Выбираем способ доставки контента и нажимаем Get Started. 4. Указываем базовые настройки как на скриншоте: Выбираем созданное S3-хранилище из списка. Указываем Restrict Bucket Access, потому что мы создали закрытое хранилище и выбираем созданную нами политику OAI. Разрешаем автоматически прописать политики в настройки S3. 5. Настраиваем заголовки, которые при запросе будут передаваться на S3. 6. Также очень важно выбрать необходимый Price Class. Если вам не нужно активно обслуживать клиентов из Индии, Африки и Азии, то вам достаточно выбрать U.S, Canada and Europe. Тогда каждый Gb данных вам обойдется всего в 0.085$. 7. Теперь возвращаемся в S3-хранилище, которое мы создали на первом шаге, и убеждаемся, что политика прописалась корректно. 8. Переходим в список точек распределения и ждём около минуты, пока сервис не развернется. 9. Чтобы проверить работоспособность сервиса, достаточно перейти на указанный в колонке Domain Name адрес: http://%cloud-font-id%.cloudfront.net/testPhoto.jpg. Теперь у вас есть работающий CDN. 10. Также, если необходимо, вы можете настроить свой домен, установить SSL сертификат и включить необходимое вам логирование. Это оставим вам в качестве самостоятельной работы. Ключевой частью системы является Lambda функция. Она как раз отвечает за обработку изображения. Поясним принцип работы CloudFront и Lambda@Edge.  AWS Lambda позволяет запускать код без выделения серверов и управления ими. Вы платите только за потраченное время вычислений — плата не взимается, когда ваш код не работает. Лямбда масштабируется автоматически и обеспечивает высокую доступность. Lambda@Edge расширяет возможности Lambda до предела, позволяя запускать код в нескольких местах AWS ближе к зрителю. Для каждого поведения кеша в раздаче Amazon CloudFront можно добавить до четырех триггеров, которые вызывают выполнение функции Lambda, если появляется одно или несколько из событий CloudFront: Принцип работы CloudFront. Источник Запрос CloudFront Viewer — функция выполняется, когда CloudFront получает запрос от средства просмотра, и перед проверкой наличия запрошенного объекта в пограничном кеше. CloudFront Origin Request — функция выполняется только тогда, когда CloudFront пересылает запрос вашему источнику. Когда запрошенный объект находится в пограничном кеше, функция не выполняется. Ответ CloudFront от источника — функция выполняется после того, как CloudFront получает ответ от источника и до кэширования объекта в ответе. Ответ CloudFront Viewer — функция выполняется до того, как запрошенный объект возвращается, в программу просмотра. Функция выполняется независимо от того, находится ли объект уже в краевом кеше. Механизм работы наших Lambda простой: мы будем передавать параметры обработки изображений прямо в URL — https://domain.ltd/tr:w-XXX,h-XXX/path/to/image.jpg. Для этого наши шаги будут следующими: Создаем функцию обработчик для Viewer-request. Её задача — принять заголовки браузера и понять, принимает ли он webp или ему нужно отдавать jpg/png и скорректировать строку запроса соответствующим образом.  В этом случае мы добавляем в начале запроса папку с именем формата /webp/tr:w-XXX,h-XXX/path/to/image.jpg либо /original/tr:w-XXX,h-XXX/path/to/image.jpg Можно обойтись без нее и просто проксировать заголовок accept, но тогда он будет являться частью cache ключа, и кеширование будет неэффективно, так как вариантов значений заголовка может быть много. 2. Далее мы создаем функцию origin-response. Она работает так:  Мы смотрим, есть ли эта картинка на S3, которую мы получили благодаря полученному коду ответа от S3. Если картинка есть, мы ничего не делаем, и просто позволяем CloudFront ее закешировать. Если картинки на S3 нет, то нам необходимо ее создать.  Так как в качестве ресурса мы используем внешний web сервер, мы делаем http запрос за картинкой, обрабатываем её в соответствии с настройками (сжимаем, изменяем размер). В качестве хранилища также можно использовать этот же bucket S3, тогда вместо http запроса вам нужно будет сделать внутренний запрос за картинкой через AWS SDK. После этого обработанную картинку сохраняем в S3 уже по новому пути и отдаём её в CloudFront. В следующий раз, когда кеш картинки потеряет актуальность, в S3 уже будет лежать нужная нам картинка, и весь алгоритм завершится на пункте b. Мы разместили наш код Lambda-функций на GitHub. Делимся им с вами, чтобы вы могли применить его в своих экспериментах. Его можно взять и сразу запустить в продакшене. Мы не настаиваем, что наш код будет самым оптимальным решением для вашего случая. Лучшего протестировать, понять, как работает наше решение, и потом уже осознанно реализовать свое. Прежде чем приступим к созданию нашей функции, поясним несколько основных частей функции origin-response: exports.handler — это основная функция, которая и будет выполняться сервисом Lambda@Edge. В первую очередь внутри этого обработчика мы получаем оригинальный путь до файла. Этим занимается класс ImageRequest. Он же отвечает за то, чтобы понять, какие трансформации необходимо произвести и в каком формате нужно отдать файл (webp либо в формате оригинала) Далее функция проверяет наличие файла на S3 — это делается просто путем определения кода ответа от origin сервера.  Если S3 ответило нам кодом 404 или 403, значит файл не найден и нужно его создать. Для этого мы отправляем запрос на наш сервер по HTTP. Скачиваем картинку, делаем необходимые трансформации через sharp библиотеку и сохраняем все на S3. Если файл найден, то мы завершаем выполнение функции и отдаём тот ответ, который и планировал отдать S3. Сам код мы снабдили дополнительными комментариями.  Давайте теперь создадим эти функции, чтобы моментально делать ресайз: Заходим в IAM и создайте роль, которая позволит Lambda функции считывать и писать данные в S3, а так же способна исполнятся. Источник 2. Добавляем политики AmazonS3FullAccess, AWSLambdaExecute. 3. На последнем шаге указываем имя. 4. Создаём две функции для viewer-request и origin-response, выбираем вариант from scratch и указываем там роль, которую создали в п.3. 5. Дальше необходимо подготовить код к загрузке. 6. Нам понадобится установленный Docker, чтобы собрать sharp с поддержкой libvips  7. Скачиваем репозиторий. 8. Выполняем скрипт build.sh. 9. Заливаем код в соответствующие функции. 10. Проверяем по ссылке. Чтобы тестировать ваши функции локально, достаточно воспользоваться:  AWS CLI и AWC SAM cd lambda/origin-response     sam local invoke -e s3-event.json Так, немного разобравшись, мы сэкономили почти 200$ ежемесячно без потери качества и скорости для пользователя. На работу и тестирования мы потратили неделю. Мы пользуемся этим решением с марта, и оно показывает себя довольно стабильно: картинки отдаются быстро и без потери качества. Маркеплейс авторских туров