AWS Lambda — это сервис вычислений, запускающий ваш код при определенных событиях и автоматически управляющий вашими вычислительными ресурсами. Обзор концепции, принципов работы, цен и тому подобного уже есть на хабре ( habrahabr.ru/company/epam_systems/blog/245949/ ), я же попробую показать практический пример использования этого сервиса.
Итак, как следует из названия поста, мы будем использовать AWS Lambda для создания архива указанных нами файлов, хранящихся на AWS S3. Поехали!
Создание новой функции в консоли AWS
AWS Lambda находится в стадии «preview», поэтому, если вы используете её в первый раз, необходимо будет заполнить запрос и подождать несколько дней получения доступа.
Для создания новой функции в консоли AWS нажимаем на кнопку Create a Lambda function и попадаем на форму задания параметров новой функции.
В первую очередь нас спрашивают название и описание новой функции:
Затем её код:
Код можно или напрямую писать в редактор или загружать специально приготовленный zip-архив. Первый вариант подходит только для кода без дополнительных зависимостей, а второй на момент написания мной этой статьи не работал через web. Поэтому на данном этапе мы создаём функцию с кодом из редактора, не меняя текст предлагаемого примера, а позже мы загрузим необходимый нам код со всем зависимостями программными средствами.
Role name определяет какими правами доступа к различным объектам AWS будет обладать функция. Не буду сейчас заострять на этом внимание, скажу только что предлагаемые по-умолчанию при создании новой роли права предоставляют доступ к AWS S3 и потому достаточны для этого примера.
Также необходимо указать выделяемое количество памяти и таймаут выполнения:
Выделяемое количество памяти влияет на цену работы функции (чем больше, чем дороже). Однако, к ней также привязаны выделяемые ресурсы процессора. Так как задача создания архива сильно зависит от ресурсов процессора, выбираем максимально доступное количество памяти — рост цены при этом полностью компенсируется уменьшением времени обработки.
Заполнив форму, нажимаем Create Lambda function и покидаем консоль AWS с тем, чтобы перейти к непосредственному созданию нашей функции.
Код функции, а также её упаковка и выгрузка в AWS
Для решения нашей задачи, будем использовать несколько сторонних библиотек, а так же библиотеку grunt-aws-lambda для удобства разработки, упаковки и выгрузки готовой функции.
Создаём packaje.json следующего содержания:
{
"name": "zip-s3",
"description": "AWS Lamda Function",
"version": "0.0.1",
"private": "true",
"devDependencies": {
"aws-sdk": "^2.1.4",
"grunt": "^0.4.5",
"grunt-aws-lambda": "^0.3.0"
},
"dependencies": {
"promise": "^6.0.1",
"s3-upload-stream": "^1.0.7",
"archiver": "^0.13.1"
},
"bundledDependencies": [
"promise",
"s3-upload-stream",
"archiver"
]
}
и устанавливаем зависимости:
npm install
Архив bundledDependencies в package.json содержит зависимости, которые будут упакованы вместе с нашей функцией при выгрузке.
После этого создаём файл index.js в котором будет располагаться код функции.
Для начала посмотрим как выглядит код функции, не делающей ничего:
exports.handler = function (data, context) {
context.done(null, '');
}
Вызов context.done сигнализирует, что работа функции завершена, при этом AWS Lambda прекращает её выполнение, считает использованное время и т.п.
Объект data содержит параметры, передаваемые в функцию. Структура этого объекта у нас будет иметь следующий вид:
{
bucket : 'from-bucket',
keys : ['/123/test.txt', '/456/test2.txt'],
outputBucket : 'to-bucket',
outputKey : 'result.zip'
}
Приступим к написанию собственно кода функции.
Подключаем необходимые библиотеки:
var AWS = require('aws-sdk');
var Promise = require('promise');
var s3Stream = require('s3-upload-stream')(new AWS.S3());
var archiver = require('archiver');
var s3 = new AWS.S3();
Создаём объекты, которые будут осуществлять архивацию файлов и потоковую загрузку получающегося архива на AWS S3.
var archive = archiver('zip');
var upload = s3Stream.upload({
"Bucket": data.outputBucket,
"Key": data.outputKey
});
archive.pipe(upload);
Создаём промис, чтобы вызвать context.done по окончании загрузки результата:
var allDonePromise = new Promise(function(resolveAllDone) {
upload.on('uploaded', function (details) {
resolveAllDone();
});
});
allDonePromise.then(function() {
context.done(null, '');
});
Получаем файлы по заданным адресам и добавляем их в архив. По окончании загрузки всех файлов, закрываем архив:
var getObjectPromises = [];
for(var i in data.keys) {
(function(itemKey) {
itemKey = decodeURIComponent(itemKey).replace(/+/g,' ');
var getPromise = new Promise(function(resolveGet) {
s3.getObject({
Bucket: data.bucket,
Key : itemKey
}, function(err, fileData) {
if (err) {
console.log(itemKey, err, err.stack);
resolveGet();
}
else {
var itemName = itemKey.substr(itemKey.lastIndexOf('/'));
archive
.append(fileData.Body, { name: itemName });
resolveGet();
}
});
});
getObjectPromises.push(getPromise);
})(data.keys[i]);
}
Promise.all(getObjectPromises).then(function() {
archive.finalize();
});
var AWS = require('aws-sdk');
var Promise = require('promise');
var s3Stream = require('s3-upload-stream')(new AWS.S3());
var archiver = require('archiver');
var s3 = new AWS.S3();
exports.handler = function (data, context) {
var archive = archiver('zip');
var upload = s3Stream.upload({
"Bucket": data.outputBucket,
"Key": data.outputKey
});
archive.pipe(upload);
var allDonePromise = new Promise(function(resolveAllDone) {
upload.on('uploaded', function (details) {
resolveAllDone();
});
});
allDonePromise.then(function() {
context.done(null, '');
});
var getObjectPromises = [];
for(var i in data.keys) {
(function(itemKey) {
itemKey = decodeURIComponent(itemKey).replace(/+/g,' ');
var getPromise = new Promise(function(resolveGet) {
s3.getObject({
Bucket: data.bucket,
Key : itemKey
}, function(err, data) {
if (err) {
console.log(itemKey, err, err.stack);
resolveGet();
}
else {
var itemName = itemKey.substr(itemKey.lastIndexOf('/'));
archive
.append(data.Body, { name: itemName });
resolveGet();
}
});
});
getObjectPromises.push(getPromise);
})(data.keys[i]);
}
Promise.all(getObjectPromises).then(function() {
archive.finalize();
});
};
Для упаковки и загрузки файлов на AWS создаём Gruntfile.js следующего содержания:
module.exports = function(grunt) {
grunt.initConfig({
lambda_invoke: {
default: {
}
},
lambda_package: {
default: {
}
},
lambda_deploy: {
default: {
function: 'zip-s3'
}
}
});
grunt.loadNpmTasks('grunt-aws-lambda');
};
И файл ~/.aws/creadentials с ключами доступа к AWS:
[default]
aws_access_key_id = ...
aws_secret_access_key = ...
Упаковываем и выгружаем нашу функцию в AWS Lambda:
grunt lambda_package lambda_deploy
Вызов созданной функции из нашего приложения
Вызывать функцию будем из приложения на java.
Для этого подготавливаем данные:
JSONObject requestData = new JSONObject();
requestData.put("bucket", "from-bucket");
requestData.put("outputBucket","to-bucket");
requestData.put("outputKey", "result.zip");
JSONArray keys = new JSONArray();
keys.put(URLEncoder.encode("/123/файл1.txt","UTF-8"));
keys.put(URLEncoder.encode("/456/файл2.txt","UTF-8"));
requestData.put("keys", keys);
И непосредственно вызываем функцию:
AWSCredentials myCredentials = new BasicAWSCredentials(accessKeyID, secretKey);
AWSLambdaClient awsLambda = new AWSLambdaClient(myCredentials);
InvokeAsyncRequest req = new InvokeAsyncRequest();
req.setFunctionName("zip-s3");
req.setInvokeArgs(requestData.toString());
InvokeAsyncResult res = awsLambda.invokeAsync(req);
Функция будет выполнена асинхронно — мы сразу получим результат, что запрос на выполнение успешно принят AWS Lambda, само же выполнение займёт некоторое время.
Автор: darvecher