Запуск бессерверного JS-проекта с GitLab

Возможно, вы слышали о таких бессерверных FaaS-решениях, как AWS Lambda. Рассказываем, как это работает на примере запуска JavaScript-проекта с GitLab.

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

Хотите узнать секрет бессерверности, не выполнив ни одной команды в терминале? Всё что нужно – учётные записи GitLab и AWS.

Создание проекта

Для начала создадим проект с помощью бессерверного шаблона. Открываем страницу нового проекта и выбираем вкладку Create from template. Прокручиваем вниз и выбираем шаблон Serverless Framework/JS.

Называем проект, создаём с помощью Create project.

Настройка учётной записи AWS

Проект создан, передаём доступ в AWS, чтобы его развернуть. Открываем консоль AWS, переходим в раздел IAM. Здесь выбираем в левой колонке Users и создаём нового пользователя с помощью кнопки Add user в верхней части списка.

Даём пользователю имя, например, gitlab-serverless. Прежде чем нажать Next, убедитесь, что включен флажок Programmatic access.

Теперь нужно предоставить пользователю соответствующие права для развёртывания бессерверных функций. На странице Permissions выбираем Attach existing policies directly и нажимаем Create policy – откроется новое окно.

Здесь нужно выбрать вкладку "JSON" и вставить следующее:

{
  "Statement": [
    {
      "Action": [
        "apigateway:*",
        "cloudformation:CancelUpdateStack",
        "cloudformation:ContinueUpdateRollback",
        "cloudformation:CreateChangeSet",
        "cloudformation:CreateStack",
        "cloudformation:CreateUploadBucket",
        "cloudformation:DeleteStack",
        "cloudformation:Describe*",
        "cloudformation:EstimateTemplateCost",
        "cloudformation:ExecuteChangeSet",
        "cloudformation:Get*",
        "cloudformation:List*",
        "cloudformation:PreviewStackUpdate",
        "cloudformation:UpdateStack",
        "cloudformation:UpdateTerminationProtection",
        "cloudformation:ValidateTemplate",
        "dynamodb:CreateTable",
        "dynamodb:DeleteTable",
        "dynamodb:DescribeTable",
        "ec2:AttachInternetGateway",
        "ec2:AuthorizeSecurityGroupIngress",
        "ec2:CreateInternetGateway",
        "ec2:CreateNetworkAcl",
        "ec2:CreateNetworkAclEntry",
        "ec2:CreateRouteTable",
        "ec2:CreateSecurityGroup",
        "ec2:CreateSubnet",
        "ec2:CreateTags",
        "ec2:CreateVpc",
        "ec2:DeleteInternetGateway",
        "ec2:DeleteNetworkAcl",
        "ec2:DeleteNetworkAclEntry",
        "ec2:DeleteRouteTable",
        "ec2:DeleteSecurityGroup",
        "ec2:DeleteSubnet",
        "ec2:DeleteVpc",
        "ec2:Describe*",
        "ec2:DetachInternetGateway",
        "ec2:ModifyVpcAttribute",
        "events:DeleteRule",
        "events:DescribeRule",
        "events:ListRuleNamesByTarget",
        "events:ListRules",
        "events:ListTargetsByRule",
        "events:PutRule",
        "events:PutTargets",
        "events:RemoveTargets",
        "iam:CreateRole",
        "iam:DeleteRole",
        "iam:DeleteRolePolicy",
        "iam:GetRole",
        "iam:PassRole",
        "iam:PutRolePolicy",
        "iot:CreateTopicRule",
        "iot:DeleteTopicRule",
        "iot:DisableTopicRule",
        "iot:EnableTopicRule",
        "iot:ReplaceTopicRule",
        "kinesis:CreateStream",
        "kinesis:DeleteStream",
        "kinesis:DescribeStream",
        "lambda:*",
        "logs:CreateLogGroup",
        "logs:DeleteLogGroup",
        "logs:DescribeLogGroups",
        "logs:DescribeLogStreams",
        "logs:FilterLogEvents",
        "logs:GetLogEvents",
        "s3:CreateBucket",
        "s3:DeleteBucket",
        "s3:DeleteBucketPolicy",
        "s3:DeleteObject",
        "s3:DeleteObjectVersion",
        "s3:GetObject",
        "s3:GetObjectVersion",
        "s3:ListAllMyBuckets",
        "s3:ListBucket",
        "s3:PutBucketNotification",
        "s3:PutBucketPolicy",
        "s3:PutBucketTagging",
        "s3:PutBucketWebsite",
        "s3:PutEncryptionConfiguration",
        "s3:PutObject",
        "sns:CreateTopic",
        "sns:DeleteTopic",
        "sns:GetSubscriptionAttributes",
        "sns:GetTopicAttributes",
        "sns:ListSubscriptions",
        "sns:ListSubscriptionsByTopic",
        "sns:ListTopics",
        "sns:SetSubscriptionAttributes",
        "sns:SetTopicAttributes",
        "sns:Subscribe",
        "sns:Unsubscribe",
        "states:CreateStateMachine",
        "states:DeleteStateMachine"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ],
  "Version": "2012-10-17"
}

Эта политика является примером, охватывающим практически всё, что может понадобиться бессерверной платформе на AWS. Но большая её часть даже не будет использована. Можно ограничить платформу в соответствии с вашими нуждами и требованиями безопасности. Как минимум понадобится доступ: cloudformation, iam, lambda, logs и функции s3 .

Нажимаем Review Policy, придумываем имя, например, GitLabServerlessPolicy. Кликаем Create policy.

После этого возвращаемся во вкладку Add user и находим недавно созданную политику (возможно, потребуется нажать значок Refresh). Устанавливаем флажок рядом с этой политикой и нажимаем Next.

Жмём на Next: Tags или переходим к обзору. Последняя страница должна выглядеть следующим образом:

После нажатия кнопки Create user будет показана страницу с учётными данными для доступа к новой учётной записи AWS. Выбираем Show рядом с secret access key и копируем его вместе с ID в укромное место.

Ввод учётных данных AWS

Вернёмся к GitLab. Нам нужно ввести данные в настройки CI/CD нашего проекта. Выберите Settings -> CI/CD в левом меню.

На этой странице необходимо развернуть раздел с переменными и ввести учётные данные AWS:

Используйте AWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY в качестве ключей для двух значений, скопированных из AWS на предыдущем шаге. Не забудьте нажать кнопку Save variables.

Развёртывание первой функции AWS Lambda

Теперь пришло время развернуть проект. Если вы делаете это на gitlab.com, у вас уже есть доступ к GitLab раннеру на 2000 бесплатных минут CI пайплайна. Если нет, то нужно настроить раннер.

Перейдите в меню CI/CD -> Pipelines слева и нажмите Run Pipeline. Для развлечения давайте введём переменную окружения с ключом A_VARIABLE и дадим ей любое значение. Это будет использоваться нашей функцией.

Нажимаем Run Pipeline, видим, как наши задания начинают выполняться. Этот шаблон проекта содержит тесты, которые будут автоматически стартовать при каждом запуске пайплайна. После их завершения задание "production" задеплоит код в AWS Lambda и создаст страницу на GitLab Pages. Через несколько минут процесс должен завершиться, и мы сможем посетить Settings -> Pages, чтобы увидеть ссылку, куда все развернулось.

В результате видим там следующее:

Введите значение и нажмите Run function. Этот инпут отправится в бессерверную функцию, результат будет выведен в поле Function Output. Обратите внимание, что здесь также присутствует значение среды, которое мы предоставили с помощью ключа A_VARIABLE.

Внесение изменений

Как насчёт простого калькулятора? Откройте Web IDE и внесите следующие изменения.

Внутри src/handler.js добавьте следующую функцию:

module.exports.add = async function(event) {
  const A = Number(event.queryStringParameters.A);
  const B = Number(event.queryStringParameters.B);
  const result = A + B;

  return {
    statusCode: 200,
    headers: {
      "Access-Control-Allow-Origin": "*"
    },
    body: result
  };
};

Откройте public/index.html и замените его на следующее:

<!DOCTYPE html>
<html>
  <head>
    <title>GitLab Serverless Framework example</title>
  </head>
  <body>
    <h3>Add two values:</h3>
    <label>A: <input type="text" id="inputA" placeholder="0" name="A"/></label>
    <label>B: <input type="text" id="inputB" placeholder="0" name="B"/></label>
    <strong>=</strong>
    <span id="functionOutput">?</span>
    <br />
    <button>Calculate!</button>

    <script>
      fetch("./stack.json").then(response => {
        response.json().then(myJson => {
          const functionUrl = myJson.ServiceEndpoint + "/add";
          const inputA = document.querySelector("#inputA");
          const inputB = document.querySelector("#inputB");
          const output = document.querySelector("#functionOutput");

          document.querySelector("button").addEventListener("click", () => {
            const A = Number(inputA.value);
            const B = Number(inputB.value);

            fetch(functionUrl + "?A=" + A + "&B=" + B)
              .then(response => response.text())
              .then(result => (output.textContent = result));
          });
        });
      });
    </script>
  </body>
</html>

Наконец, в serverless.yml добавьте функцию "add" под "hello":

functions:
  hello:
    handler: src/handler.hello
    events:
      - http:
          path: hello
          method: get
          cors: true
  add:
    handler: src/handler.add
    events:
      - http:
          path: add
          method: get
          cors: true

Выполните коммит в главную ветку. Это автоматически приведёт к запуску нового пайплайна. Можно посетить CI/CD -> Pipelines и посмотреть, как это работает.

После завершения развёртывания страница проекта должна выглядеть следующим образом:

Вуаля, мы только что создали вашу собственную бессерверную функцию и развернули её без единой команды в терминале.

Как вам туториал? Хотели бы видеть больше таких статей?

Источники

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

Библиотека программиста
18 октября 2017

Шпаргалка по Git, в которой представлены основные команды

Git сегодня - это очень популярная система контроля версий. Поэтому шпаргал...
admin
23 февраля 2017

Git за полчаса: руководство для начинающих

В последние годы популярность git демонстрирует взрывной рост. Эта система ...