Давайте попробуем создать программу на Python 3.6, которая будет переводить цветное фото в черно-белый формат и которая использует OpenCV. Причем мы будем реализовывать эту задачу через API, которое принимает данные бинарного типа и возвращает данные такого же типа (формата JPEG). При этом есть два важных условия, которым должно соответствовать решение этой задачи:
- Отсутствие готового сервера.
- Отсутствие оплаты (за исключением лишь тех случаев, когда трафик превышает бесплатный пакет Lambda).
Первые шаги
Сперва необходимо установить Docker. Для данного примера необходимо использовать сервис от AWS, который называется lamba, который позволяет легко развернуть нужную функцию и ее зависимости, а также быстро подключить ее к API. В свою очередь, для создания API нами будет использоваться решение, которое называется API Gateway. Это решение предоставляется AWS.
Чтобы упростить это руководство, мы развернем код, загрузим его в Lambda, используя веб-консоль AWS. Также мы напишем код функции внутри консоли AWS, чтобы максимально упростить процесс. Во всех остальных случаях необходимо выполнить развертывание, используя AWS CLI.
Итак, нам необходимо выполнить следующую последовательность действий.
- Начать со входа в консоль AWS, и осуществить поиск Lambda.
- Сделать клик по кнопке «Create function» (создать функцию).
- Задаем настройки функции. Пусть наша функция будет называться lambda-demo. Нам необходимо удостовериться, что мы работаем с Python 3.6 в качестве среды выполнения и создать новую роль из шаблонов политики AWS.
- После того, как функция будет создана, будет предоставлен определенный шаблон кода в консоли Lambda.
import json def lambda_handler(event, context): return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') }
Чтобы сразу вызвать данную функцию, настраивается тестовое событие. Для этого необходимо нажать Test и настроить первое тестовое событие. Для целей этой статьи, шаблон по умолчанию работает правильно.
После того, как будет создано тестовое событие, необходимо нажать Test. В логах функции должен быть получен такой результат.
{ "statusCode": 200, "body": "\"Hello from Lambda!\"" }
Превосходно. Теперь давайте попробуем создать что-то полезное. Например, построить функцию, которая будет получать изображение и делать его черно-белым. Для реализации этой задачи мы будем использовать OpenCV. Помимо этого, использование OpenCV может прекрасно подойти для этой задачи. Она показывает, как столь полезная библиотека может быть добавлена в Lambda с относительной легкостью.
Сейчас мы выполним следующие действия.
- Генерируем пакет Lambda для OpenCV, заточенный под Python.
- Загружаем пакет в Lambda Layers, чтобы было возможно его использование в какой-угодно функции, которая создается.
- Осуществляется импорт OpenCV в функцию Lambda.
Генерация пакета Lambda
Был собран понятный инструмент – образ Docker, который умеет получать любой pip-пакет и генерировать архив формата ZIP, который может быть загружен в Lambda Layers. Если желаете подробно изучить этот инструмент, его можно найти на GitHub.
Если у вас уже Docker установлен, то достаточно выполнить следующую команду в терминале.
docker run --rm -v $(pwd):/package tiivik/lambdazipper opencv-python
Это все! В имеющейся рабочей папке можно найти zip-файл, который будет называться opencv-python.
Один из наиболее полезных безсерверных наборов инструментов – serverless. Тем не менее он не будет использоваться сейчас. Ведь нужно согласиться, что изобретение велосипеда – не всегда подходящая идея, если не учитывать те случаи, когда нужно изучить внутреннее его устройство.
Несмотря на то, что такие функциональные фреймворки, как serverless существуют, лучше найти ряд корневых функций, абстрагируемых данными фреймворками.
Давайте узнаем, что наш инструмент абстрагирует от нас.
Если вы посмотрите на package.sh, то вы увидите он им была выполнена инструкция установки pip install, в качестве аргумента которого использовался opencv-python. Все это выполнялось с использованием среды amazonlinux:2017.03, которая в определенной степени имитирует среду AWS Lambda. Вы можете изучить среду выполнения в Dockerfile.
Загрузка Lambda Layers для использования в любой созданной функции
Теперь давайте откроем opencv-python.zip в Lambda Layers, чтобы этот пакет можно было использовать во всех функциях. Относитесь к Layers как к данным, которые могут быть использованы в любой из функций, которые были написаны ранее. Это не только модули Python. Могут также использоваться части кодов, бинарные файлы, и так далее.
Откройте панель Layers в AWS Lambda и сделайте клик по кнопке «Create Layer» (Создать слой).
Укажите название слоя, его описание и загрузите zip-архив. Удостоверьтесь в том, что была выбрана правильная среда выполнения (в случае с нашим примером такой является Python 3.6). Далее нажмите «создать слой» (Create layer).
На момент создания этого материала можно лишь загружать файл размером в 50MB. Хорошо, что наш архив имеет гораздо меньший объем. Учтите то, что Lambda указывает ограничение развертывания пакета в 250 MB.
После того, как функция будет создана, должно появиться уведомление:
Successfully created layer opencv-python version 1.
Превосходно. Давайте вернемся к lambda-demo функции и добавим слой, который будет называться opencv-python, в среду выполнения нашей функции. Для этого необходимо нажать Layers > Add a layer и выбрать слой opencv-python.
Импорт OpenCV
Давайте попробуем стандартным способом импортировать библиотеку.
import json import cv2 def lambda_handler(event, context): return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') }
После того, как будет нажата кнопка «Test», будет получен следующий результат.
{ "errorMessage": "Unable to import module 'lambda_function'" }
По определенной причине, у Lambda не получилось найти пакет. По умолчанию, все слои Lambda монтируются в /opt. Давайте уберем наш импорт модуля см2 и посмотрим на то, что внутри /opt.
import json #import cv2 from os import listdir def lambda_handler(event, context): print(listdir("/opt")) return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') }
В журналах функции мы можем увидеть наш модуль cv2 и numpy в /opt.
[‘bin’, ‘cv2’, ‘numpy’, ‘numpy-1.16.2.dist-info’, ‘opencv_python-4.0.0.21.dist-info’]
По умолчанию, /opt/bin занесен в переменную среды $PATH. Пользователь всегда может удостовериться в этом, почитав официальную документацию AWS. Тем не менее наши модули слоя расположены в /opt/, а не в /opt/bin. Итак, давайте внесем /opt в $PATH, чтобы Lambda увидела наш пакет.
Для этого в разделе Environment Variables необходимо указать такие параметры среды.
- Ключ: PYTHONPATH
- значение: /opt/
Как правило, достаточно лишь обычного импорта пакета без изменения пути, но в случае с нами это необходимо для среды Lambda для обнаружения пакета.
Давайте внесем следующие изменения в код.
import json import cv2 def lambda_handler(event, context): print(cv2.__version__) return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') }
Далее сохраните внесенные изменения, после чего кликните на Test. В консоли вы увидите 4.0.0, что говорит о том, какая версия OpenCV используется.
Отлично, OpenCV был запущен в Lambda.
Давайте продолжим реализацию корневой логики программы – конвертацию картинок в черно-белый формат. Для этого необходимо обновить код функции Lambda.
import json import cv2 import base64 def write_to_file(save_path, data): with open(save_path, "wb") as f: f.write(base64.b64decode(data)) def lambda_handler(event, context): # Записываем содержимое в файл write_to_file("/tmp/photo.jpg", event["body"]) # Чтение изображения image = cv2.imread("/tmp/photo.jpg") # Конвертация в черно белый gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # Запись чернобелого изображения в /tmp cv2.imwrite("/tmp/gray.jpg", gray) # Конвертация черно белого изображения в кодировку utf-8 base64 with open("/tmp/gray.jpg", "rb") as imageFile: str = base64.b64encode(imageFile.read()) encoded_img = str.decode("utf-8") # Возвращаем данные в API Gateway в base64. # API Gateway будет обрабатывать конверсию обратно в бинарный вид # Настройка типа содержимого в заголовке как image/jpeg. return { "isBase64Encoded": True, "statusCode": 200, "headers": { "content-type": "image/jpeg"}, "body": encoded_img }
API, который будет настроен фактически сейчас, будет принимать бинарное изображение от клиента. После этого оно будет конвертировано в base64 через AWS API Gateway и передано в Lambda.
Естественно, API Gateway еще не настроен, так что тестирование кода приведет к неудаче. Тем не менее перед тем, как будет выполнен переход к настройке API, который вызывает Lambda, можно протестировать API в консоли Lambda, предоставив base64 в теле событий кодированную картинку.
Далее перенастраиваем Test с этим содержимым. Если интересно, это изображение кошки, которое конвертировано в base64.
{ "body" : "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AAAAAAtjcm9wUmVjdFRvcGxvbmcAAAAAADhCSU0D7QAAAAAAEACQAAAAAQACAJAAAAABAAI4QklNBCYAAAAAAA4AAAAAAAAAAAAAP4AAADhCSU0EDQAAAAAABAAAAB44QklNBBkAAAAAAAQAAAAeOEJJTQPzAAAAAAAJAAAAAAAAAAABADhCSU0nEAAAAAAACgABAAAAAAAAAAI4QklNA/UAAAAAAEgAL2ZmAAEAbGZmAAYAAAAAAAEAL2ZmAAEAoZmaAAYAAAAAAAEAMgAAAAEAWgAAAAYAAAAAAAEANQAAAAEALQAAAAYAAAAAAAE4QklNA/gAAAAAAHAAAP////////////////////////////8D6AAAAAD/////////////////////////////A+gAAAAA/////////////////////////////wPoAAAAAP////////////////////////////8D6AAAOEJJTQQIAAAAAAAQAAAAAQAAAkAAAAJAAAAAADhCSU0EHgAAAAAABAAAAAA4QklNBBoAAAAAA3cAAAAGAAAAAAAAAAAAAABAAAAAQAAAACEAUwBjAHIAZQBlAG4AcwBoAG8AdAAgADIAMAAxADkALQAwADMALQAxADAAIABhAHQAIAAwADAALgAyADYALgAyADcAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAEAAAAAAABudWxsAAAAAgAAAAZib3VuZHNPYmpjAAAAAQAAAAAAAFJjdDEAAAAEAAAAAFRvcCBsb25nAAAAAAAAAABMZWZ0bG9uZwAAAAAAAAAAQnRvbWxvbmcAAABAAAAAAFJnaHRsb25nAAAAQAAAAAZzbGljZXNWbExzAAAAAU9iamMAAAABAAAAAAAFc2xpY2UAAAASAAAAB3NsaWNlSURsb25nAAAAAAAAAAdncm91cElEbG9uZwAAAAAAAAAGb3JpZ2luZW51bQAAAAxFU2xpY2VPcmlnaW4AAAANYXV0b0dlbmVyYXRlZAAAAABUeXBlZW51bQAAAApFU2xpY2VUeXBlAAAAAEltZyAAAAAGYm91bmRzT2JqYwAAAAEAAAAAAABSY3QxAAAABAAAAABUb3Ag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MCIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSIyOERCRTU2MDNFOTg1OTU0NjZEQjg5ODVFRTU2MEI0MCIgZGM6Zm9ybWF0PSJpbWFnZS9qcGVnIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiBwaG90b3Nob3A6SUNDUHJvZmlsZT0iZi5sdXggcHJvZmlsZSIgeG1wOkNyZWF0ZURhdGU9IjIwMTktMDMtMTBUMDA6MjY6MjkrMDI6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAzLTEwVDAwOjI3OjMwKzAyOjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDE5LTAzLTEwVDAwOjI3OjMwKzAyOjAwIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MDE4MDExNzQwNzIwNjgxMTgyMkE5MEM0MkExQjY0QzAiIHN0RXZ0OndoZW49IjIwMTktMDMtMTBUMDA6Mjc6MzArMDI6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKE1hY2ludG9zaCkiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjAyODAxMTc0MDcyMDY4MTE4MjJBOTBDNDJBMUI2NEMwIiBzdEV2dDp3aGVuPSIyMDE5LTAzLTEwVDAwOjI3OjMwKzAyOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDwvcmRmOlNlcT4gPC94bXBNTTpIaXN0b3J5PiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg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" }
Вызов этого теста будет успешным.
Response: { "isBase64Encoded": true, "statusCode": 200, "headers": { "content-type": "image/jpeg" }, "body": "/9j/4AJRgAB.....P+WqHNf//Z" <- long base64 string of black and white image here }
Теперь мы можем настраивать API, которая вызывает эту функцию Lambda.
Настройка API
Теперь необходимо открыть консоль AWS API Gateway. Для этого нажимаем Create API.
Теперь создается новый REST API, и ему дается имя и описание. В этом случае, API дадим имя lambda-demo.
В Resources > Actions выбираем Create Method, чтобы определить метод POST.
Для типа интеграции выбираем Lambda Function и выбираем функцию Lambda из выпадающего меню. Включите интеграцию Use Lambda Proxy, и нажмите Save.
Нам необходимо, чтобы API мог обрабатывать бинарные данные.
В Settings > Binary Media Types выбираем Add Binary Media Type, и определяем таким образом типа двоичных данных.
image/jpeg image/png */*
Затем кликаем по Save Changes.
Переходим снова в метод POST.
Под Method Response добавляем Content-Type Response Header и указываем тип image/jpeg.
Перед публикацией API можно его протестировать путем нажатия кнопки Client Test.
В нашем случае, мы предоставляем само тело изображения в base64, а не объект json. Для того, чтобы было более удобно, вы можете вставить необработанную строку base64 из следующей ссылки в поле Request Body.
В качестве ответа будет base64 черно-белой фотографии.
Сделайте клик по Actions > Deploy API.
Теперь необходимо создать новый этап развертывания, назвать его правильно (например, development) и нажать Deploy.
Наш API теперь опубликован и отлично работает. Вы получите url, в котором он развернут.
https://XXXXX.execute-api.XXXX.amazonaws.com/development
Давайте теперь испытаем. Для этого делаем следующее:
1. Загружаем то же изображение в локальную среду.
curl https://i.imgur.com/offvirS.jpg -o kitty.jpg
2. Через Post-запрос отправляем картинку в бинарном виде. Как следствие, получено черно-белое изображение, которое сохранилось с названием kitty_bw.jpg.
curl -X POST —data-binary @kitty.jpg https://XXXXX.execute-api.eu-central-1.amazonaws.com/development -o kitty_bw.jpg
Здесь необходимо учесть и то, что для простоты понимания мы упростили это руководство, не использовав здесь обработку ошибок, проверку запроса и настройку авторизации. Все эти возможности также содержатся в API Gateway и в коде функции Lambda.