- Что такое aws lambda?
- Аддоны в aws lambda
- Деплой с помощью aws cli
- Загрузка в aws lambda
- Зачем использовать лямбда функцию в python?
- Использование с filter()
- Использование с map()
- Локальное тестирование
- Настройка рабочего окружения
- Пример 1
- Пример 2
- Синтаксис для определения анонимной функции
- Создаём аддон (локально)
- Создаём лямбда-функцию
- Тестирование с помощью aws cli
- Требование 2: 64-bit
- Требование 3: node.js версии 4.3
- Требование 4: инструменты для сборки c кода (с поддержкой с 11)
- Требование №1: linux
- Упаковка лямбда-функции и аддона
Что такое aws lambda?
Цитируя документацию:
AWS Lambda это вычислительный сервис, в который вы можете загружить свой код, который будет запущен на инфраструктуре AWS по вашему поручению. После загрузки кода и создания того, что мы называем лямбда-функцией, сервис AWS Lambda берёт на себя ответственность за контроль и управление вычислительными мощностями, необходимыми для выполнения данного кода. Вы можете использовать AWS Lambda следующими способами:
AWS Lambda — очень крутая платформа, но поддерживает всего несколько языков: Java, Node.js и Python. Что же делать, если мы хотим выполнить некоторый код на С ? Ну, вы определённо можете слинковать код на С с Java-кодом, да и Python умеет это делать.
Но мы посмотрим, как это сделать на Node.js. В мире Node.js интеграция с кодом на С традиционно происходит через аддоны. Аддон на С к Node.js представляет собой скомпилированный (нативный) модуль Node.js, который может быть вызван из JavaScript или любого другого Node.js-модуля.
Аддоны к Node.js это большая тема — если вы их раньше не писали, возможно, стоит почитать что-то вроде этой серии постов или более узкоспециализировано об интеграции С и Node.js в веб-проектах. Есть и хорошая книга на эту тему.
Аддоны в aws lambda
Чем же использование аддонов в AWS Lambda отличается от классического сценария их использования? Самая большая проблема состоит в том, что AWS Lambda не собирается вызывать node-gyp или любой другой инструмент сборки перед запуском вашей функции — вы должны собрать полностью функциональный бинарный пакет.
Это означает, как минимум, то, что вы должны собрать ваш аддон на Linux перед деплоем в AWS Lambda. А если у вас есть какие-нибудь зависимости, то собирать нужно не просто на Linux, а на Amazon Linux. Есть и другие нюансы, о которых я расскажу дальше.
Эта статья не о построении сложных смешанных приложений на Node.js C в инфраструктуре Amazon, она лишь описывает базовые техники сборки и деплоя таких программ. По остальным темам можно обратиться к документации Amazon — там есть куча примеров.
Я собираюсь написать С аддон, который будет содержать функцию, принимающую три числа и возвращающий их среднее значение. Да, я знаю, это вот как-раз то, что можно написать только на С . Мы выставим данную функцию в качестве доступной для использования через AWS Lambda и протестируем её работу через AWS CLI.
Деплой с помощью aws cli
Есть два способа деплоя кода в AWS Lambda — через веб-интерфейс и через утилиты командной строки (CLI). Я планирую использовать CLI, поскольку данный подход кажется мне более универсальным. Однако, всё описанное далее при желании можно сделать и через веб-интерфейс.
Если у вас ещё нет AWS-аккаунта — сейчас самое время его создать. Дальше нужно создать Администратора. Полная инструкция есть в документации Амазона. Не забудьте добавить созданному Администратору роль AWSLambdaBasicExecutionRole.
Загрузка в aws lambda
Теперь мы можем создать лямбда-функцию с помощью команды «lambda create-function».
aws lambda create-function
--region us-west-2
--function-name average
--zip-file fileb://../average.zip
--handler index.averageHandler
--runtime nodejs4.3
--role arn:aws:iam::729041145942:role/lambda_execute
Большинство параметров говорят сами за себя — но если вы не знакомы с AWS Lambda, то параметр “role” может для вас выглядеть несколько загадочно. Как говорилось выше, для работы с AWS Lambda вам необходимо было создать роль, имеющую разрешение AWSLambdaBasicExecutionRole.
Если всё пройдёт хорошо, вы должны получить JSON с ответом, содержащим некоторую дополнительную информацию о только что задеплоеной лямбда-функции.
Зачем использовать лямбда функцию в python?
Основная роль лямбда-функции в Python лучше описана в сценариях, когда мы используем их анонимно внутри другой функции. В Python лямбда-функция может использоваться в качестве аргумента для функций более высокого порядка, которые принимают другие функции в качестве аргументов.
#the function table(n) prints the table of n
def table(n):
return lambda a:a*n # a will contain the iteration variable i and a multiple of n is returned at each function call
n = int(input("Enter the number:"))
b = table(n) #the entered number is passed into the function table. b will contain a lambda function which is called again and again with the iteration variable i
for i in range(1,11):
print(n,"X",i,"=",b(i)) #the lambda function b is called with the iteration variable iВывод
Enter the number:10 10 X 1 = 10 10 X 2 = 20 10 X 3 = 30 10 X 4 = 40 10 X 5 = 50 10 X 6 = 60 10 X 7 = 70 10 X 8 = 80 10 X 9 = 90 10 X 10 = 100
Лямбда-функция обычно используется со встроенными функциями Python, функциями filter() и map().
Использование с filter()
Встроенная функция filter() принимает в качестве аргумента функцию и список. Это эффективный способ отфильтровать все элементы последовательности. Он возвращает новую последовательность, в которой функция оценивает значение True.
Рассмотрим следующий пример, в котором мы отфильтровываем единственное нечетное число из данного списка.
#program to filter out the tuple which contains odd numbers lst = (10,22,37,41,100,123,29) oddlist = tuple(filter(lambda x:(x%3 == 0),lst)) # the tuple contains all the items of the tuple for which the lambda function evaluates to true print(oddlist)
Вывод
(37, 41, 123, 29)
Использование с map()
Функция map() в Python принимает функцию и список. Дает новый список, который содержит все измененные элементы, возвращаемые функцией для каждого элемента.
#program to filter out the list which contains odd numbers lst = (10,20,30,40,50,60) square_list = list(map(lambda x:x**2,lst)) # the tuple contains all the items of the list for which the lambda function evaluates to true print(square_tuple)
Вывод
(100, 400, 900, 1600, 2500, 3600)
Локальное тестирование
Теперь у нас есть файл index.js, экспортирующий обработчик вызовов AWS Lambda и мы можем попробовать загрузить его туда. Но давайте вначале протестируем его локально. Есть отличный модуль, который называется lambda-local — он может помочь нам с тестированием.
npm install -g lambda-local
После его установки мы можем вызвать нашу лямбда-функцию по имени обработчика “averageHandler” и передать ему наше тестовое событие. Давайте создадим файл sample.js и напишем в него:
module.exports = {
op1: 4,
op2: 15,
op3: 2
};
Теперь мы можем выполнить нашу лямбду командой:
lambda-local -l index.js -h averageHandler -e sample.js
Logs
------
START RequestId: 33711c24-01b6-fb59-803d-b96070ccdda5
END
Message
------
7
Как и ожидалось, результат равен 7 (среднее значение чисел 4, 15 и 2).
Настройка рабочего окружения
Есть причина, по которой Java с её слоганом ”
” стала популярной — и эта причина в сложности распространения скомпилированного бинарного кода между разными платформами. Java не решила все эти проблемы идеально («напиши однажды, отлаживай везде»), но с тех пор мы прошли длинный путь. Чаще всего мы блаженно забываем о платформенно-специфичных проблемах, когда пишем код на Node.
npmnode-gyp
Многие из этих удобств, однако, теряются при использовании Amazon Lambda — нам необходимо полностью собрать нашу Node.js-программу (и её зависимости). Если мы используем нативный аддон, это означает, что собирать всё необходимое нам придётся на той же архитектуре и платформе, где работает AWS Lambda (64-битный Linux), а кроме того нужно будет использовать ту же самую версию рантайма Node.js, который используется в AWS Lambda.
Пример 1
# a is an argument and a 10 is an expression which got evaluated and returned.
x = lambda a:a 10
# Here we are printing the function object
print(x)
print("sum = ",x(20))
Выход:
В приведенном выше примере мы определили анонимную функцию лямбда a: a 10, где a – аргумент, а a 10 – выражение. Данное выражение оценивается и возвращает результат. Вышеупомянутая лямбда-функция такая же, как и обычная функция.
def x(a): return a 10 print(sum = x(10))
Пример 2
Несколько аргументов лямбда-функции
x = lambda a,b: a*b
print("mul = ", x(20,10))Вывод:
mul = 200
Синтаксис для определения анонимной функции
lambda arguments: expression
Может принимать любое количество аргументов и имеет только одно выражение. Это полезно, когда требуются функциональные объекты.
Рассмотрим следующий пример лямбда-функции.
Создаём аддон (локально)
Нам понадобиться создать два Node.js-проекта. Один будет нашим С аддоном, который вообще не будет содержать в себе ничего относящегося к AWS Lambda — просто классический нативный аддон. Второй же проект будет лямбда-функций в терминах AWS Lambda — то есть модулем Node.js, который будет импортировать нативный аддон и брать на себя вызов его функционала. Если вы хотите попробовать на своей машине — весь код здесь, а конкретно этот пример — в папке lambda-cpp.
Давайте начнём с аддона.
mkdir lambda-cpp
mkdir lambda-cpp/addon
cd lambda-cpp/addon
Для создания аддона нам понадобятся три файла — код на С , package.json чтобы сказать Node.js как обращаться с этим аддоном и binding.gyp для процесса сборки. Давайте начнём с самого простого — binding.gyp
{
"targets": [
{
"target_name": "average",
"sources": [ "average.cpp" ]
}
]
}
Это, вероятно, простейший вариант файла binding.gyp, который только возможно создать — мы задаём имя цели и исходники для компиляции. При необходимости здесь можно наворотить сложнейшие вещи, отражающие опции компилятора, пути к внешним каталогам, библиотекам и т.д. Просто помните, что всё, на что вы ссылаетесь должно быть статически слинковано в бинарник и собрано под архитектуру x64.
Теперь давайте создадим package.json, который должен определять точку входа данного аддона:
Создаём лямбда-функцию
А теперь давайте создадим, собственно, лямбда-функцию для AWS Lambda. Как вы (возможно) уже знаете, для AWS Lambda нам необходимо создать обработчик, который будет вызываться каждый раз, когда произойдёт некоторое событие. Этот обработчик получит описание данного события (которое может быть, например, операцией изменения данных в S3 или DynamoDB) в виде JS-объекта. Для этого теста мы используем простое событие, описываемое следующим JSON:
{
op1: 4,
op2: 15,
op3: 2
}
Мы можем сделать это прямо в папке аддона, но я предпочитаю создать отдельный модуль Node.js и подтянуть локальный аддон как npm-зависимость. Давайте создадим новую папку где-то рядом с lambda-cpp/addon, пусть она будет называться lambda-cpp/lambda.
cd ..
mkdir lambda
cd lambda
Теперь создадим файл index.js и напишем в нём следующий код:
exports.averageHandler = function(event, context, callback) {
const addon = require('average');
var result = addon.average(event.op1, event.op2, event.op3)
callback(null, result);
}
Заметьте, что мы сослались на внешнюю зависимость “average”. Давайте создадим файл package.json, в котором опишем ссылку на локальный аддон:
Тестирование с помощью aws cli
Теперь, когда мы задеплоили нашу лямбда-функцию, давайте протестируем её с помощью того же интерфейса командной строки. Вызовем нашу функцию, передав ей описание того же самого события, что и в прошлый раз.
Требование 2: 64-bit
Это, возможно, следовало назвать требованием №1… По тем же самым причинам, о которых рассказано выше — вам нужно создать для деплоя zip-файл с бинарниками под архитектуру x64. Так что ваш старенький запылившийся 32-битный Linux на виртуалке не подойдёт.
Требование 3: node.js версии 4.3
На момент написания данной статьи AWS Lambda поддерживает Node.js 0.10 и 4.3. Вам абсолютно точно лучше выбрать 4.3. В будущем актуальная версия может измениться — следите за этим. Я люблю использовать nvm для установки и удобного переключения между версиями Node.js. Если у вас ещё нет этого инструмента — пойдите и установите его прямо сейчас:
Требование 4: инструменты для сборки c кода (с поддержкой с 11)
Когда вы разрабатываете аддон для Node.js v4 , вы должны использовать компилятор с поддержкой С 11. Последние версии Visual Studio (Windows) и XCode (Mac OS X) подойдут для разработки и тестирования, но, поскольку нам нужно будет собрать всё под Linux, нам понадобиться g 4.7 (или более свежий). Вот как установить g 4.9 на Mint/Ubuntu:
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt-get update
sudo apt-get install g -4.9
sudo update-alternatives --install /usr/bin/g g /usr/bin/g -4.9 20
Требование №1: linux
Мы, конечно же, можем разрабатывать / тестировать / отлаживать лямбда-функции с аддонами на OS X или Windows, но когда мы дойдём до этапа деплоя в AWS Lambda — нам понадобится zip-файл со всем содержимым модуля Node.js — включая все его зависимости. Нативный код, входящий в состав этого zip-файла, должен запускаться в инфраструктуре AWS Lambda.
А это значит, что собирать его нам нужно будет только под Linux. Обратите внимание, что в этом примере я не использую никаких дополнительных библиотек — мой код на С полностью независимый. Как я объясню детальнее дальше — если вам нужны зависимости от внешних библиотек, понадобиться пойти немного глубже.
Я буду делать все мои эксперименты в этой статье на Linux Mint.
Упаковка лямбда-функции и аддона
Наиболее важный (и часто обсуждаемый в интернете) шаг во всём этом процессе — это убедиться в том, что весь ваш модуль будет упакован в zip-файл корректно. Вот наиболее важные вещи, которые нужно проверить:
- Файл index.js должен быть в корневой папке zip-файла. Вы не должны упаковывать саму папку /lambda-addon/lambda — только её содержимое. Другими словами — если вы распакуете созданный zip файл в текущую папку — файл index.js должен оказаться в этой же папке, а не в подпапке.
- Папка node_modules и всё её содерижмое должно быть упаковано в zip-файл.
- Вы должны собрать аддон и упаковать его в zip-файл на правильной платформе (см. выше требования — Linux, x64 и т.д.)
В папке, где находится index.js, упакуйте все файлы, которые должны быть задеплоены. Я создам zip-файл в родительской папке.
zip -r ../average.zip node_modules/ average.cpp index.js binding.gyp package.json
*Обратите внимание на ключ “-r” — нам нужно упаковать всё содержимое папки node_modules. Проверьте полученный файл командой less, должно получиться что-то такое:
less ../average.zip
Archive: ../average.zip
Length Method Size Cmpr Date Time CRC-32 Name
-------- ------ ------- ---- ---------- ----- -------- ----
0 Stored 0 0% 2021-08-17 19:02 00000000 node_modules/
0 Stored 0 0% 2021-08-17 19:02 00000000 node_modules/average/
1 Stored 1 0% 2021-08-17 17:39 6abf4a82 node_modules/average/output.txt
478 Defl:N 285 40% 2021-08-17 19:02 e1d45ac4 node_modules/average/package.json
102 Defl:N 70 31% 2021-08-17 15:03 1f1fa0b3 node_modules/average/binding.gyp
0 Stored 0 0% 2021-08-17 19:02 00000000 node_modules/average/build/
115 Defl:N 110 4% 2021-08-17 19:02 c79d3594 node_modules/average/build/binding.Makefile
3243 Defl:N 990 70% 2021-08-17 19:02 d3905d6b node_modules/average/build/average.target.mk
3805 Defl:N 1294 66% 2021-08-17 19:02 654f090c node_modules/average/build/config.gypi
0 Stored 0 0% 2021-08-17 19:02 00000000 node_modules/average/build/Release/
0 Stored 0 0% 2021-08-17 19:02 00000000 node_modules/average/build/Release/.deps/
0 Stored 0 0% 2021-08-17 19:02 00000000 node_modules/average/build/Release/.deps/Release/
125 Defl:N 67 46% 2021-08-17 19:02 daf7c95b node_modules/average/build/Release/.deps/Release/average.node.d
0 Stored 0 0% 2021-08-17 19:02 00000000 node_modules/average/build/Release/.deps/Release/obj.target/
0 Stored 0 0% 2021-08-17 19:02 00000000 node_modules/average/build/Release/.deps/Release/obj.target/average/
1213 Defl:N 386 68% 2021-08-17 19:02 b5e711d9 node_modules/average/build/Release/.deps/Release/obj.target/average/average.o.d
208 Defl:N 118 43% 2021-08-17 19:02 c8a1d92a node_modules/average/build/Release/.deps/Release/obj.target/average.node.d
13416 Defl:N 3279 76% 2021-08-17 19:02 d18dc3d5 node_modules/average/build/Release/average.node
0 Stored 0 0% 2021-08-17 19:02 00000000 node_modules/average/build/Release/obj.target/
0 Stored 0 0% 2021-08-17 19:02 00000000 node_modules/average/build/Release/obj.target/average/
5080 Defl:N 1587 69% 2021-08-17 19:02 6aae9857 node_modules/average/build/Release/obj.target/average/average.o
13416 Defl:N 3279 76% 2021-08-17 19:02 d18dc3d5 node_modules/average/build/Release/obj.target/average.node
12824 Defl:N 4759 63% 2021-08-17 19:02 f8435fef node_modules/average/build/Makefile
554 Defl:N 331 40% 2021-08-17 15:38 18255a6e node_modules/average/average.cpp
237 Defl:N 141 41% 2021-08-17 19:02 7942bb01 index.js
224 Defl:N 159 29% 2021-08-17 18:53 d3d59efb package.json
-------- ------- --- -------
55041 16856 69% 26 files
(type 'q' to exit less)
Если вы не видите содержимого папки node_modules внутри zip-файла или если файлы имеют дополнительный уровень вложенности в иерархии папок — ещё раз перечитайте всё, что написано выше!
