Запуск бессерверного 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
и посмотреть, как это работает.
После завершения развёртывания страница проекта должна выглядеть следующим образом:
Вуаля, мы только что создали вашу собственную бессерверную функцию и развернули её без единой команды в терминале.