- PVSM.RU - https://www.pvsm.ru -

Фото: Jesse Darland [1] с Unsplash [2]
В этой статье речь пойдёт о том, как перенести процесс предварительной обработки изображений с сервера приложений на абсолютно бессерверную архитектуру платформы AWS.
Если веб-приложение позволяет загрузить изображение, скорее всего его необходимо обработать перед тем, как показать пользователю.
В статье будет рассказано о бессерверной архитектуре на основе платформы AWS, которая предоставляет широкие возможности для масштабирования.
В одном из моих последних проектов (веб-приложение для торговли, при работе с которым пользователю необходимо загружать изображение товара) исходное изображение сначала обрезается по соотношению сторон 4:3. Затем оно преобразуется в три разных формата, используемых в различных элементах пользовательского интерфейса: 800x600, 400x300 и 200x150.
Будучи разработчиком фреймворка Ruby on Rails, я в первую очередь решил попробовать пакеты RubyGem, а именно Paperclip [3] или Dragonfly [4], которые используют для обработки изображений набор ImageMagick.
Это довольно простой подход, но у него есть свои недостатки:
В общем, если в приложении обрабатывается достаточно большое количество изображений, это решение не масштабируется.
Если присмотреться к процессу предварительной обработки изображений, можно понять, что в большинстве случаев нет необходимости в его выполнении непосредственно на сервере приложений. Это особенно актуально, если всегда осуществляются одни и те же преобразования, которые не требуют других данных, кроме самого изображения. Так было и в моём случае: я всегда только и делал, что создавал несколько изображений разного размера, оптимизируя их уровень качества.
Как только стало понятно, что эту задачу можно легко отделить от остальной логики приложения, на ум сразу же пришла мысль о бессерверном решении, которое будет просто брать исходное изображение в качестве входных данных и выполнять все необходимые преобразования.
Оказалось, сервис AWS Lambda идеально подходит для этой цели. Он способен обрабатывать тысячи запросов в секунду, при этом платить надо только за фактическое время вычислений. Если же код не выполняется, то и денег с вас требовать не будут.
Сервис AWS S3 предлагает неограниченное хранилище по низкой цене, а служба AWS SNS обеспечивает простой обмен сообщениями по модели «издатель-подписчик» для микросервисов, распределённых систем и бессерверных приложений. Наконец, AWS Cloudfront используется в качестве сети доставки контента для изображений, хранящихся в S3.
Сочетание этих четырёх услуг AWS даёт нам мощное решение для обработки изображений при минимальных затратах.
Создание различных версий изображения из одного исходного начинается с загрузки оригинала в AWS S3. Затем с помощью AWS SNS запускается функция AWS Lambda, которая отвечает за создание новых версий и их повторную загрузку в AWS S3. Более подробно процесс выглядит так:

Эта архитектура легко масштабируется, так как каждое загруженное изображение инициирует новое выполнение кода Lambda для обработки конкретного запроса. Таким образом, множественное выполнение кода позволяет обрабатывать тысячи изображений одновременно.
Дисковое пространство и вычислительные ресурсы сервера приложений не используются, так как все данные хранятся в S3 и обрабатываются службой Lambda.
И наконец, настройка сети доставки контента для S3 очень проста и позволяет поддерживать высокую скорость загрузки в любой точке мира.
Реализация этого решения не очень сложная, поскольку в основном (за исключением кода Lambda, который выполняет предварительную обработку изображений) включает в себя лишь настройку. Далее в статье подробно описывается, как настроить архитектуру AWS. А чтобы вы могли в полной мере оценить её работу, также приводится код AWS Lambda для изменения размера загруженного изображения.
Чтобы опробовать его самостоятельно, вам понадобится учётная запись AWS. Вы можете создать её и воспользоваться бесплатным стартовым пакетом AWS здесь [5].
Прежде всего необходимо настроить новый топик SNS (Simple Notification Service), куда AWS будет публиковать сообщения каждый раз, когда в S3 загружается новое изображение. Такое сообщение содержит S3-ключ объекта, используемый впоследствии функцией Lambda для извлечения и обработки изображения.
Из консоли AWS зайдите на страницу SNS [6], нажмите Create topic и введите название топика, например, image-preprocessing.

Затем нужно изменить политику топика, чтобы позволить бакету S3 публиковать сообщения.
На странице топика нажмите Actions -> Edit Topic Policy, выберите Advanced view, добавьте следующий блок JSON (с указанием собственных имён ресурсов Amazon (arn) в строках Resource и SourceArn) в массив Statement и обновите политику:
{
"Sid": "ALLOW_S3_BUCKET_AS_PUBLISHER",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": [
"SNS:Publish",
],
"Resource": "arn:aws:sns:us-east-1:AWS-OWNER-ID:image-preprocessing",
"Condition": {
"StringLike": {
"aws:SourceArn": "arn:aws:s3:*:*:YOUR-BUCKET-NAME"
}
}
}
Пример полного JSON-текста политики здесь [7].
Теперь нужно подготовить структуру папок в S3, в которых будут храниться исходные и обработанные изображения. В данном примере мы будем создавать версии изображения в двух размерах: 800x600 и 400x300.
Из консоли AWS откройте страницу S3 [8] и создайте новый бакет. Я назову его image-preprocessing-example. Далее нужно создать в бакете папки с названиями originals, 800x600 и 400x300.

Каждый раз при загрузке нового изображения в папку originals S3 должен публиковать сообщение в топике image-preprocessing, чтобы это изображение можно было обработать.
Для настройки публикации таких сообщений откройте бакет S3 через консоль AWS, нажмите Properties -> Events -> Add notification и заполните следующие поля:

Здесь мы задаём правило для генерации события каждый раз, когда создаётся новый объект (чекбокс ObjectCreate) внутри папки originals (поле Prefix), и публикации этого события в SNS-топике image-preprocessing.
Требуется создать функцию Lambda, которая будет скачивать изображения из папки S3, обрабатывать их и загружать обработанные версии обратно в S3. Но сначала необходимо настроить роль IAM, чтобы функция Lambda могла получить доступ к необходимой папке S3.
Из консоли AWS перейдите на страницу IAM:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1495470082000",
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::YOUR-BUCKET-NAME/*"
]
}
]
}
Строка Resource относится к нашему бакету в S3. Нажмите Review, введите имя политики, например, AllowAccessOnYourBucketName, и создайте политику.


Создание роли Lambda

Прикрепление политики к роли Lambda

Сохранение роли
Далее необходимо настроить функцию Lambda, чтобы она считывала сообщения из топика image-preprocessing и генерировала изменённые версии изображений.
Начнём с создания новой функции Lambda.
Из консоли AWS перейдите на страницу Lambda [10], нажмите Create function и введите имя новой функции, например, ImageResize. Выберите среду выполнения (в данном случае Node.js 6.10) и ранее созданную роль IAM.

Затем нужно добавить SNS в число триггеров, чтобы функция Lambda вызывалась каждый раз, когда новое сообщение публикуется в теме image-preprocessing.
Для этого нажмите SNS в списке триггеров, выберите image-preprocessing в списке топиков SNS и нажмите Add.

Теперь нужно загрузить код, который будет обрабатывать событие S3 ObjectCreated, что включает в себя получение загруженного изображения из папки originals, его обработку и повторную загрузку в соответствующие папки для изменённых изображений.
Код можно скачать здесь [11].
Единственный элемент, который необходимо загрузить в функцию Lambda, — это архив version1.1.zip [12], который содержит файл index.js и папку node_modules.

Чтобы предоставить функции Lambda достаточное количество ресурсов для обработки изображения, можно увеличить объём памяти до 256 Мб, а максимальное время выполнения (timeout) до 10 секунд. Потребность в ресурсах зависит от размера изображения и сложности преобразований.

Сам код довольно прост и предназначен для демонстрации интеграции AWS.
Сначала определяется функция-обработчик (export.handler). Она вызывается внешним триггером. В данном случае — сообщением, опубликованным в SNS, которое содержит S3-ключ объекта загруженного изображения.
В первую очередь, она анализирует JSON-текст сообщения о событии, чтобы извлечь имя бакета S3, S3-ключ объекта загруженного изображения, а также имя файла (последняя часть ключа).
После получения имени бакета и ключа объекта загруженное изображение извлекается с помощью операции s3.getObject, а затем передаётся в функцию для изменения размера. Переменная SIZE содержит размеры изображений, которые нужно получить. Они соответствуют именам папок S3, в которые загружаются преобразованные изображения.
var async = require('async');
var AWS = require('aws-sdk');
var gm = require('gm').subClass({ imageMagick: true });
var s3 = new AWS.S3();
var SIZES = ["800x600", "400x300"];
exports.handler = function(event, context) {
var message, srcKey, dstKey, srcBucket, dstBucket, filename;
message = JSON.parse(event.Records[0].Sns.Message).Records[0];
srcBucket = message.s3.bucket.name;
dstBucket = srcBucket;
srcKey = message.s3.object.key.replace(/+/g, " ");
filename = srcKey.split("/")[1];
dstKey = "";
...
...
// Download the image from S3
s3.getObject({
Bucket: srcBucket,
Key: srcKey
}, function(err, response){
if (err){
var err_message = 'Cannot download image: ' + srcKey;
return console.error(err_message);
}
var contentType = response.ContentType;
// Pass in our image to ImageMagick
var original = gm(response.Body);
// Obtain the size of the image
original.size(function(err, size){
if(err){
return console.error(err);
}
// For each SIZES, call the resize function
async.each(SIZES, function (width_height, callback) {
var filename = srcKey.split("/")[1];
var thumbDstKey = width_height +"/" + filename;
resize(size, width_height, imageType, original,
srcKey, dstBucket, thumbDstKey, contentType,
callback);
},
function (err) {
if (err) {
var err_message = 'Cannot resize ' + srcKey;
console.error(err_message);
}
context.done();
});
});
});
}
Функция изменения размера преобразует исходное изображение при помощи библиотеки gm, в частности, она изменяет размер изображения, при необходимости обрезает его и снижает качество до 80%. Затем она загружает изменённое изображение в S3, используя операцию s3.putObject, и указывает ACL: public-read, чтобы новое изображение стало общедоступным.
var resize = function(size, width_height, imageType,
original, srcKey, dstBucket, dstKey,
contentType, done) {
async.waterfall([
function transform(next) {
var width_height_values = width_height.split("x");
var width = width_height_values[0];
var height = width_height_values[1];
// Transform the image buffer in memory
original.interlace("Plane")
.quality(80)
.resize(width, height, '^')
.gravity('Center')
.crop(width, height)
.toBuffer(imageType, function(err, buffer) {
if (err) {
next(err);
} else {
next(null, buffer);
}
});
},
function upload(data, next) {
console.log("Uploading data to " + dstKey);
s3.putObject({
Bucket: dstBucket,
Key: dstKey,
Body: data,
ContentType: contentType,
ACL: 'public-read'
},
next);
}
], function (err) {
if (err) {
console.error(err);
}
done(err);
}
);
};
Теперь можно проверить, всё ли работает верно, загрузив изображение в папку originals. Если всё было сделано правильно, мы получим соответствующие преобразованные версии загруженного изображения в папках 800x600 и 400x300.
В видеоролике ниже вы можете увидеть три окна: слева — папка originals, посередине — папка 800x600, а справа — папка 400x300. После загрузки файла в папку originals два других окна обновляются, чтобы проверить, были ли созданы изображения.
И вуаля, вот они ;)
Теперь, когда изображения созданы и загружены в S3, их необходимо доставить конечным пользователям. Чтобы повысить скорость загрузки, можно использовать сеть доставки контента Cloudfront. Для этого:
Процесс создания сети займёт какое-то время, поэтому подождите, пока статус CDN не изменится с In Progress на Deployed.
После того как сеть развёрнута, можно использовать имя домена вместо ссылки на бакет S3. Например, если имя вашего домена Cloudfront — 1234-cloudfront-id.cloudfront.net, то вы сможете получить доступ к папке обработанных изображений по ссылкам 1234-cloudfront-id.cloudfront.net/400x300/FILENAME [14] и 1234-cloudfront-id.cloudfront.net/800х600/FILENAME [15]

В Cloudfront есть множество других важных параметров, но в рамках этой статьи мы их рассматривать не будем. Чтобы получить более подробную инструкцию по настройке вашей сети доставки контента, обратитесь к Руководству от Amazon [16].
Автор: vchernov
Источник [17]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/amazon/278867
Ссылки в тексте:
[1] Jesse Darland: http://unsplash.com/photos/cAQfkfLSA80?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText
[2] Unsplash: http://unsplash.com/search/photos/perform?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText
[3] Paperclip: http://github.com/thoughtbot/paperclip
[4] Dragonfly: http://github.com/markevans/dragonfly
[5] здесь: http://aws.amazon.com/free/
[6] страницу SNS: https://signin.aws.amazon.com/signin?redirect_uri=https%3A%2F%2Fconsole.aws.amazon.com%2Fsns%2Fv2%2Fhome%3Fstate%3DhashArgs%2523%26isauthcode%3Dtrue&client_id=arn%3Aaws%3Aiam%3A%3A015428540659%3Auser%2Fsns&forceMobileApp=0
[7] здесь: http://github.com/domangi/image-preprocessing-lambda/blob/master/sns-policy-example.json
[8] страницу S3: http://signin.aws.amazon.com/signin?redirect_uri=https%3A%2F%2Fs3.console.aws.amazon.com%2Fs3%2Fhome%3Fstate%3DhashArgs%2523%26isauthcode%3Dtrue&client_id=arn%3Aaws%3Aiam%3A%3A015428540659%3Auser%2Fs3&forceMobileApp=0
[9] Create Policy: http://signin.aws.amazon.com/signin?redirect_uri=https%3A%2F%2Fconsole.aws.amazon.com%2Fiam%2Fhome%3Fregion%3Dus-east-1%26state%3DhashArgs%2523%252Fpolicies%2524new%253Fstep%253Dedit%26isauthcode%3Dtrue&client_id=arn%3Aaws%3Aiam%3A%3A015428540659%3Auser%2Fiam&forceMobileApp=0
[10] страницу Lambda: http://signin.aws.amazon.com/signin?redirect_uri=https%3A%2F%2Fconsole.aws.amazon.com%2Fsns%2Fv2%2Fhome%3Fstate%3DhashArgs%2523%26isauthcode%3Dtrue&client_id=arn%3Aaws%3Aiam%3A%3A015428540659%3Auser%2Fsns&forceMobileApp=0
[11] здесь: http://github.com/domangi/image-preprocessing-lambda
[12] version1.1.zip: http://github.com/domangi/image-preprocessing-lambda/blob/master/version1.1.zip
[13] страницу CloudFront: http://signin.aws.amazon.com/signin?redirect_uri=https%3A%2F%2Fconsole.aws.amazon.com%2Fcloudfront%2Fhome%3Fstate%3DhashArgs%2523%26isauthcode%3Dtrue&client_id=arn%3Aaws%3Aiam%3A%3A015428540659%3Auser%2Fcloudfront&forceMobileApp=0
[14] 1234-cloudfront-id.cloudfront.net/400x300/FILENAME: https://1234-cloudfront-id.cloudfront.net/400x300/FILENAME
[15] 1234-cloudfront-id.cloudfront.net/800х600/FILENAME: https://1234-cloudfront-id.cloudfront.net/800%D1%85600/FILENAME
[16] Руководству от Amazon: http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/GettingStarted.html
[17] Источник: https://habr.com/post/354394/?utm_campaign=354394
Нажмите здесь для печати.