Бессерверный API с Terraform: GO и AWS [Часть 2]

Бессерверный API с Terraform: GO и AWS [Часть 2]

29 марта 2022 г.

В части 1 я рассмотрел основы настройки серверной части для отслеживания состояния инфраструктуры. и развертывание его на AWS. На этот раз я сосредоточусь на создании оставшейся части — API. Итак, без лишних слов, приступим.


В демонстрационных целях я создам конечную точку API /users, которая позволит выполнять над ней операции CRUD. Я начну с каталога iac/api, где будут определены все лямбда-выражения, персистентный слой и шлюз API.


То же упражнение, что и в части 1. Переменные находятся в файле variables.tf, вычисляемые переменные – в файле locals.tf, а основная точка сборки – в файле main.tf. Давайте рассмотрим main.tf, так как все остальное очень похоже на модуль prerequisites из части 1.


Во-первых, я настраиваю бэкенд:


терраформировать {


бэкенд "s3" {


регион = "eu-центральный-1"


ведро = "проект-123-удаленное-состояние"


ключ = "проект-123-remote-state.tfstate"


dynamodb_table = "проект-123-tf-statelock"


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


Есть несколько вариантов справиться с этим:


1) С помощью файла конфигурации:


Создайте файл конфигурации, обычно у каждой среды будет свой файл, и заполните его переменными:


``` ударить


iac/api/backend_config


регион = "eu-центральный-1"


ведро = "проект-123-удаленное-состояние"


ключ = "проект-123-remote-state.tfstate"


dynamodb_table = "проект-123-tf-statelock"


Наконец, примените эту конфигурацию:


``` ударить


инициализация terraform -backend-config=backend_config


2) Использование аргументов командной строки:


``` ударить


terraform init -backend-config="region=$REGION,bucket=$BUCKET,key=$KEY,dynamodb_table=$DDB_TABLE"


Независимо от опции, блок terraform.backend можно оставить пустым, а весь модуль инициировать с динамическими значениями.


Прямо под внутренней конфигурацией я определил лямбда-политику и роль. В двух словах, это определение того, к чему будет разрешен доступ моей Lambda, и оно варьируется в зависимости от потребностей приложения:


``` ударить


данные "template_file" "lambda_policy" {


шаблон = файл ("templates/lambda_policy.json")


данные "template_file" "лямбда_роль" {


шаблон = файл ("шаблоны/лямбда_роль.json")


ресурс "aws_iam_policy" "политика" {


name = "${local.env}-${var.prefix}-policy"


description = "политика, разрешающая лямбде использовать указанные ресурсы"


политика = data.template_file.lambda_policy.rendered


ресурс "aws_iam_role" "роль" {


name = "${local.env}-${var.prefix}-роль"


accept_role_policy = data.template_file.lambda_role.rendered


ресурс "aws_iam_role_policy_attachment" "policy_attachment" {


роль = aws_iam_role.role.name


policy_arn = aws_iam_policy.policy.arn


Как видно из приведенного выше фрагмента, я предпочитаю хранить объявления политик в отдельных файлах .json, чтобы файл main.tf оставался СУХИМ.


Следующий шаг Таблица Dynamodb. Я буду использовать его в качестве слоя сохраняемости для этого примера:


``` ударить


модуль "dynamodb_table" {


source = "terraform-aws-modules/dynamodb-table/aws"


имя = local.ddb_users_table


hash_key = "имя пользователя"


атрибуты = [


имя = "имя пользователя"


тип = "С"


теги = {


Env = локальная.env


В этом нет ничего особенного, за исключением того, что я буду использовать «имя пользователя» в качестве хеш-ключа для уникальной идентификации моих документов. Если вам нужна дополнительная информация о Dynamodb, я считаю [этот ресурс] (https://www.dynamodbguide.com/what-is-dynamo-db) очень полезным.


Затем я определяю лямбда-выражения, необходимые для выполнения операций CRUD над ресурсом /users. Я кратко проведу вас через одно определение лямбды, так как остальные лямбды выглядят точно так же, и меняются только имена и описания функций:


``` ударить


модуль "create_user_lambda" {


источник = "../модули/aws/лямбда"


имя_функции = "создать_пользователя"


lambda_path = переменная.lambda_path


description = "создать пользовательскую лямбду, часть ресурса /users CRUD для управления созданием пользователя"


role_arn = aws_iam_role.role.arn


среда = {


ENV = локальная.env


РЕГИОН = вар.регион


DDB_TABLE_NAME = local.ddb_users_table


теги = {


Env = локальная.env


Первое, что стоит упомянуть, это то, что я использую собственный модуль Lambda в качестве источника. Вы можете проверить это [здесь] (https://github.com/Daniel1984/serverless-api/blob/main/iac/modules/aws/lambda/main.tf). Это помогает сохранить мои определения Lambda СУХИМИ, абстрагируя общую функциональность и устанавливая значения конфигурации, которые в противном случае мне пришлось бы вводить снова и снова. Каждый раз, когда я запускаю «terraform plan» и «terraform apply», этот модуль будет использовать значения «function_name» и «lambda_path» для выделения, сборки и развертывания моих Lambdas, если в исходном коде обнаружено изменение.


Следующая остановка — определение API-шлюза:


``` ударить


данные "template_file" "apigw_policy" {


шаблон = файл ("${path.module}/templates/apigw_policy.json")


данные "template_file" "api_spec" {


шаблон = файл ("шаблоны/api.yaml")


варс = {


role_arn = aws_iam_role.role.arn


регион = вар.регион


create_user_lambda_arn = модуль.create_user_lambda.function_arn


update_user_lambda_arn = модуль.update_user_lambda.function_arn


get_user_lambda_arn = модуль.get_user_lambda.function_arn


delete_user_lambda_arn = модуль.delete_user_lambda.function_arn


ресурс "aws_api_gateway_rest_api" "rest_api" {


имя = "бессерверный API"


описание = "бессерверный API"


тело = data.template_file.api_spec.рендеринг


политика = data.template_file.apigw_policy.rendered


ресурс "aws_api_gateway_deployment" "client-example-api" {


rest_api_id = aws_api_gateway_rest_api.rest_api.id


stage_name = var.api_version


depend_on = [aws_api_gateway_rest_api.rest_api]


переменные = {


api_version = md5 (файл ("$ {path.module}/templates/api.yaml"))


жизненный цикл {


create_before_destroy = истина


Здесь происходит несколько вещей, но самая интересная — это спецификация открытого API. Вы можете проверить это [здесь] (https://github.com/Daniel1984/serverless-api/blob/main/iac/api/templates/api.yaml). Как и в случае с политиками, я помещаю их в отдельный файл templates/api.yaml, чтобы все было более организованно. Обратите внимание, что на этот раз, когда я использую файл шаблона спецификации API, я передаю несколько переменных. В основном это делается для того, чтобы сообщить каждому ресурсу, какую лямбду запускать при его вызове.


Из документации swagger:


Затем определение OpenAPI может использоваться инструментами создания документации для отображения API, инструментами генерации кода для создания серверов и клиентов на различных языках программирования, инструментами тестирования и многими другими вариантами использования.


Так что terraform будет делать, он возьмет мою спецификацию и создаст шлюз API в AWS. Это делается путем назначения отображаемого шаблона спецификации aws_api_gateway_rest_api.rest_api.body. Это также упрощает разработку ресурсов REST, поскольку большинство редакторов кода имеют расширения swagger, которые позволяют просматривать изменения в режиме реального времени, а также гарантируют, что спецификация действительна.


Что касается обработчиков, я использовал GO для их реализации. Я не буду вдаваться в подробности, так как он был быстро собран для демонстрационных целей. Вы можете проверить реализацию [здесь] (https://github.com/Daniel1984/serverless-api/tree/main/api/lambdas).


Предполагая, что стек prerequisites развернут, теперь я могу развернуть стек API. Сначала я должен убедиться, что terraform будет иметь доступ к AWS API:


``` ударить


экспортировать AWS_ACCESS_KEY_ID=**


экспортировать AWS_SECRET_ACCESS_KEY=**


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


``` ударить


компакт-диск /IAC/API/


план терраформирования


terraform применить --auto-approve


Файл /iac/api/outputs.tf содержит свойства, которые будут распечатаны после завершения работы terraform apply. Если все пойдет хорошо, я должен увидеть вывод, подобный этому:


``` ударить


invoke_url = "https://dorb127v21.execute-api.eu-central-1.amazonaws.com/v1"


Теперь, имея этот URL-адрес, я могу использовать curl, чтобы проверить, все ли работает так, как ожидалось:


``` ударить


создать документ


curl -X POST https://dorb127v21.execute-api.eu-central-1.amazonaws.com/v1/users \


-H 'Тип содержимого: приложение/json' \


-d '{"имя пользователя": "foo"}'


получить документ


curl -X ПОЛУЧИТЬ https://dorb127v21.execute-api.eu-central-1.amazonaws.com/v1/users/foo


обновить документ


curl -X ПОСТАВИТЬ https://dorb127v21.execute-api.eu-central-1.amazonaws.com/v1/users/foo \


-H 'Тип содержимого: приложение/json' \


-d '{"fname": "bar", "lname": "baz", "age": 100}'


удалить документ


curl -X УДАЛИТЬ https://dorb127v21.execute-api.eu-central-1.amazonaws.com/v1/users/foo


Надеюсь, вы узнали что-то полезное. Если вам нужна дополнительная информация о модулях, которые я использовал в этом примере, обратитесь к terraform [реестр] (https://registry.terraform.io/). Вы также можете найти исходный код и отслеживать ход этого проекта здесь.



Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE