В этой статье я объясню, как создать веб-сервис REST, написанный на Node.js и развернутый на AWS Lambda λ. Я также собираюсь использовать Serverless Framework ⚡, чтобы сделать развертывание действительно простым.

TL; DR: Напишите какую-нибудь функцию JavaScript для вызова (вашу службу), напишите функцию для обработки HTTP-запроса, который выглядит как это, создайте небольшой файл serverless.yaml и запустите serverless deploy. Вуаля, облачный код.

Пример кода: https://github.com/bishbashbosh123/lambda-example/

Услуги по услугам

Нет лучшего способа использовать эту мощную платформу микросервисов, чем создать службу, которая просто вызывает другую службу 😁 В этом случае я собираюсь создать веб-службу REST, которая будет получать электронную почту с сервера электронной почты (классический POP3) и позволяет отправлять почту. Это практически бесполезно, но мне нравится идея отправить электронное письмо по почте.

В конце концов, я хочу иметь прекрасный масштабируемый отказоустойчивый веб-сервис со следующими конечными точками:

GET /emails Чтобы получить список сообщений электронной почты в вашем почтовом ящике (детали заголовка)
GET /emails/{index} Чтобы получить конкретное сообщение электронной почты (тело и все)
POST /emails Чтобы отправить электронное письмо

… Которые принимают и возвращают JSON.

Функции AWS Lambda можно вызывать / запускать только через другой сервис AWS. Например, они могут быть инициированы документом, добавленным в DynamoDB или событием S3, или чем-то, что нужно обработать в очереди SQS. Но мы хотим обрабатывать запросы, отправленные в конечную точку HTTP, которую мы можем создать в AWS API Gateway.

Шлюз API может позволить вам настроить разрешенную структуру запросов, отправляемых на конечные точки, позволяя шлюзу проверять их за вас, предоставлять ответы об ошибках, коды ответов HTTP и т. Д. Это требует большой настройки. Однако, чтобы упростить ситуацию и позволить вам обрабатывать все это в своем приложении, AWS представила «Интеграцию прокси» в API Gateway. Это означает, что теперь вы можете выбрать вариант «Настроить как ресурс прокси», и весь HTTP-запрос будет отправлен в вашу лямбда-функцию. В свою очередь, ваша функция должна возвращать не только содержимое ответа, но и код состояния HTTP и любые заголовки, которые вы хотите вернуть. Шлюз делает как можно меньше.

Материал JavaScript

Итак, я создал модуль JavaScript (EmailServices.js), который предоставляет следующие функции:

  • getEmails (): Array [{index, from, subject, date}]
    Возвращает массив последних 10 электронных писем, находящихся в вашем электронном письме. сервер (без тела)
  • getEmail (index): {from, subject, date, body}
    Предоставляет вам указанный адрес электронной почты
  • sendEmail ({from, to, subject, body})
    Отправляет электронное письмо

… И мне это так нравится, что я хочу открыть это миру. Я буду использовать бессерверный фреймворк, чтобы добиться этого.

Serverless.yaml

Первое, что нужно сделать при использовании Serverless Framework, - это создать serverless.yaml. Это сообщает ему, какие функции Lambda вы хотите, какие события вы хотите их запускать (например, http, s3, schedule), какие HTTP-методы вы хотите использовать, в какую группу безопасности AWS их помещать. и т. д. Варианты, предлагаемые вам для этого файла, немного объясняются здесь и очень подробно здесь, но это очень минимальный вариант:

# Serverless definition file
# — — — — — — — — — — — — —
# Defines Lambda functions to be deployed to AWS using the Serverless Framework.
# http://serverless.com
service: email-services
provider:
    name: aws
    runtime: nodejs6.10
    region: eu-west-2
    memorySize: 128
    timeout: 30
    environment: ${file(env.yml):${self:provider.stage}}
functions:
    getEmail:
        handler: lambdahandlers.getEmail
        events:
            - http:
                path: emails/{id}
                method: get
                cors: 
                    origins:
                        - ‘*’

[Мой полностью]

В разделе поставщик вы указываете среду, в которой мы хотим выполнить развертывание. Одна из замечательных особенностей Serverless Framework - это абстракция, которую она дает вам на разных платформах. Это умное решение, позволяющее сделать выбор платформы менее важным; Если через несколько лет Azure окажется более рентабельной, было бы неплохо иметь этот вариант переключения без особых проблем.

Переменные среды

Стоит отметить бит environment в этом YAML:

environment: ${file(env.yml):${self:provider.stage}}

Это способ предоставления некоторых значений переменных среды для нашего развернутого кода. Эта строка сообщает Serverless о необходимости вставки значений из файла с именем env.yml (пример), в частности объекта в этом файле, названного в честь нашего этапа развертывания. Всякий раз, когда мы развертываем код в AWS с помощью Serverless, он развертывает его на определенной стадии. По умолчанию - dev. Этот механизм используется в API Gateway и Serverless для помощи в организации различных этапов развертывания вашего проекта. У вас может быть Production, QA, UAT, и вы хотите развернуть код в каждой из этих сред отдельно. Итак, в моем проекте у меня есть файл под названием env.yml, который содержит YAML следующим образом:

# — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -
# Environment variables
# — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -
dev:
  EMAIL_POP3_HOST: “pop.gmail.com”
  EMAIL_POP3_USERNAME: “[email protected]EMAIL_POP3_PASSWORD: “blahblahblahblah”
  EMAIL_POP3_PORT: 995
  EMAIL_POP3_TLS: true
  EMAIL_SMTP_HOST: “smtp.gmail.com”
  EMAIL_SMTP_USERNAME: “[email protected]EMAIL_SMTP_PASSWORD: “blahblahblahblah”
  EMAIL_SMTP_PORT: 465
  EMAIL_SMTP_TLS: true

(Он не привязан к репозиторию GitHub, поэтому, если вы подыгрываете, вам нужно будет написать свое собственное)

Эти значения будут применяться как переменные среды в процессе Lambda. Я буду использовать эти переменные позже для настройки модуля EmailServices.

Лямбда-обработчики

В файле serverless.yaml для каждой лямбда-функции вы можете указать такие вещи, как метод HTTP для обработки (получение, публикация и т. Д.), Источники, от которых шлюз API должен принимать запросы от ( например, '*', когда вам просто все равно) и как будет выглядеть путь к конечной точке REST. Вам также необходимо указать функцию JavaScript, которая будет ее обрабатывать. Эти функции могут иметь следующие обязанности:

  • извлечение параметров из запроса
    (event.pathParameters или event.queryStringParameters)
  • извлечение тела запроса (event.body)
  • извлечение заголовков полномочий из запроса для проверки (event.headers.Authorization)
  • создание ответа из всего, что поступает из ваших служб
    (например, {statusCode: 200, headers: {}, body: {}})

В моем случае serverless ожидает найти модуль JavaScript lambdaHandlers.js, который экспортирует функцию с именем getEmail. Не пытайтесь поместить его в папку и ссылаться на него, например, handlers / lambda.getEmail или что-нибудь в этом роде; Это не сработает, поэтому сохраните его вместе с serverless.yaml.

Эти функции-обработчики Lambda будут вызываться со следующими тремя параметрами:

  • событие - объект, содержащий подробную информацию о запросе (заголовки, параметры, пользовательский агент и т. д. - Пример)
  • context - объект, содержащий подробную информацию о вашей лямбда-функции и среде. (Пример) (http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html)
  • обратный вызов - для вызова с вашим ответом, который будет отправлен обратно клиенту. Поскольку мы используем прокси-режим шлюза API, возвращаемый объект должен иметь объект statusCode, headers и body.

Итак, я создал файл с названием lambdaHandlers.js и создал в нем такие функции:

getEmail : (event, context, callback) => {
  context.callbackWaitsForEmptyEventLoop = false
  const emailServices = createEmailServices()
  // Get the index parameter out of the event
  const index = event.pathParameters.id
  emailServices.getEmail(index)
  .then(email => {
    // Create a ‘success’ response object containing the e-mail we 
    // got back from emailServices.getEmail()
    callback(null, responses.success(email))
  })
  .catch(error => {
    callback(null, responses.error(error))
  })
}

Стоит отметить…

context.callbackWaitsForEmptyEventLoop - обычно всегда устанавливайте для этого параметра значение false, если вы не понимаете цикл событий Node.js и не уверены, что ваш код и его зависимости не оставляют никаких setInterval или process.nextTick ждут. В противном случае ваша лямбда-функция не будет «завершена» (даже если вы вызовете обратный вызов), и вы получите ошибку тайм-аута от своей лямбда-функции. Раньше существовал метод context.succeed (), но он устарел.

const emailServices = createEmailServices () - Это функция, которую я создал для получения переменных среды и передачи их в EmailServices:

function createEmailServices() {
  // Configure e-mail services
  const emailServices = new EmailServices(
    process.env.EMAIL_POP3_HOST,
    process.env.EMAIL_POP3_USERNAME,
    process.env.EMAIL_POP3_PASSWORD,

...и так далее.

Итак, все, что мне нужно сделать в lambdaHandlers.js, - это добавить две мои другие функции для обработки getEmails и sendEmail, а также иметь пару шаблонов объекты ответа…

const responses = {
  success: (data={}, code=200) => {
    return {
      'statusCode': code,
      'headers': responseHeaders,
      'body': JSON.stringify(data)
    }
  },
  error: (error) => {
    return {
      'statusCode': error.code || 500,
      'headers': responseHeaders,
      'body': JSON.stringify(error)
    }
  }
}

… И отсортируйте заголовки ответов, чтобы разрешить запросы из разных источников:

const responseHeaders = {
  'Content-Type': 'application/json',
  // Required for CORS support to work
  'Access-Control-Allow-Origin': '*',  
  // Required for cookies, authorization headers with HTTPS
  'Access-Control-Allow-Credentials': true
}

Полностью: lambdaHandlers.js

Когда у нас есть эти функции, мы можем сначала попробовать вызвать их локально, чтобы убедиться, что они работают.

Вызов локально

Установите бессерверный интерфейс командной строки, чтобы мы могли просто вызвать его из командной строки:

npm install -g serverless

… А затем используйте его так:

serverless invoke local -f getEmail

Добавьте -p 'example.json', если вы хотите отправить в функцию какие-либо параметры или тело. Файл JSON должен выглядеть примерно так:

{
  "body": "{\"from\": \"[email protected]\",\"to\": \"[email protected]\",\"subject\": \"Lambda\",\"body\": \"Just saying.\"}",
  "pathParameters": {},
  "headers": {}
}

Обратите внимание, что тело передается в виде строки, поэтому вы должны (к сожалению) сериализовать его в файле JSON в одну беспорядочную строку.

Если вы довольны тем, что ваши функции обрабатывают запросы так, как вы считаете нужным, разверните их. Разверните их прямо сейчас.

Развертывание

Нам нужно предоставить Serverless некоторые учетные данные, чтобы возиться с нашей учетной записью AWS. Есть гид здесь и даже видео, но в основном…

Создайте учетную запись AWS, если вы еще этого не сделали. Затем вам нужно перейти в раздел IAM и создать нового пользователя для вашей учетной записи, который Serverless будет использовать для создания ваших новых служб Lambda и API Gateway для вас. Это должен быть пользователь с «программным доступом» (т. Е. Пользователь, который использует API AWS, а не веб-страницу консоли). Serverless рекомендует предоставить новому пользователю полную политику AdministratorAccess, что кажется чрезмерным. В зависимости от вашей паранойи вы можете либо сделать это, либо использовать Access Advisor в IAM, чтобы сузить круг необходимых политик. Но их нужно много, поэтому просто сделайте копию (или загрузите CSV) идентификатора ключа доступа и секретного ключа доступа и храните их где-нибудь в очень-очень надежном месте.

Есть несколько способов предоставить эти ключи бессерверному серверу. Мой любимый способ - создать файл с именем учетные данные (без расширения) в каталоге с именем .aws в вашем домашний каталог. (например, C: \ Users \ LeroyJenkins \ .aws \ credentials) и добавьте к нему следующие строки:

[serverless]
aws_access_key_id=AKIAKBJIH3WIVA3Q33AF
aws_secret_access_key=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

Замените эти ключи идентификатором ключа доступа и секретным ключом, с которого вы сделали копию ранее.

Теперь вернитесь в каталог своего проекта и вызовите это:

serverless deploy --aws-profile serverless

Если наш план составлен, в нем должны быть перечислены некоторые созданные конечные точки, например:

GET https://1ggbl5kb54.execute-api.eu-west-2.amazonaws.com/dev/emails
GET https://1ggbl5kb54.execute-api.eu-west-2.amazonaws.com/dev/emails/{index}
POST https://1ggbl5kb54.execute-api.eu-west-2.amazonaws.com/dev/emails

… Бум 💥, бессерверный код где-то загромождает облако. Аккуратный недорогой, отказоустойчивый микросервис. Он предоставляет никому не нужную услугу, но она есть, на всякий случай.

Исправление проблем

Если у вас возникли какие-либо проблемы с вашими службами, которые не выполняют то, что вы ожидаете, хорошее место для начала - зайти в Консоль AWS и найти:

API Gateway → API → ‹ваш API› → Stages → ‹dev или что-то еще›

… И включить журналы CloudWatch для вашей службы. Журналы можно найти в разделе CloudWatch консоли AWS. Любые журналы console.log () / stdout / stderr будут в конечном итоге там, просто помните о дополнительных расходах, которые могут возникнуть, если вы получите много журналов (в настоящее время 0,57 доллара США за загруженный ГБ, 0,03 доллара США за ГБ в архиве в месяц). В качестве альтернативы, в вашем приложении можно использовать что-то вроде Papertrail для записи журналов в их службу, размер которой составляет до 100 МБ бесплатно.

Дай мне знать, как у тебя дела.