Использование С в AWS Lambda / Хабр

Что такое 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.

Про сертификаты:  Регистрация на ЕГЭ и ОГЭ 2022 | LANCMAN SCHOOL

Если всё пройдёт хорошо, вы должны получить 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.

Про сертификаты:  Как настроить Apache 2.2 на работу с клиентскими сертификатами? | - IT-блог для начинающих

А это значит, что собирать его нам нужно будет только под Linux. Обратите внимание, что в этом примере я не использую никаких дополнительных библиотек — мой код на С полностью независимый. Как я объясню детальнее дальше — если вам нужны зависимости от внешних библиотек, понадобиться пойти немного глубже.

Я буду делать все мои эксперименты в этой статье на Linux Mint.

Упаковка лямбда-функции и аддона

Наиболее важный (и часто обсуждаемый в интернете) шаг во всём этом процессе — это убедиться в том, что весь ваш модуль будет упакован в zip-файл корректно. Вот наиболее важные вещи, которые нужно проверить:

  1. Файл index.js должен быть в корневой папке zip-файла. Вы не должны упаковывать саму папку /lambda-addon/lambda — только её содержимое. Другими словами — если вы распакуете созданный zip файл в текущую папку — файл index.js должен оказаться в этой же папке, а не в подпапке.
  2. Папка node_modules и всё её содерижмое должно быть упаковано в zip-файл.
  3. Вы должны собрать аддон и упаковать его в 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-файла или если файлы имеют дополнительный уровень вложенности в иерархии папок — ещё раз перечитайте всё, что написано выше!

Оцените статью
Мой сертификат
Добавить комментарий