Бессерверный 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/). Вы также можете найти исходный код и отслеживать ход этого проекта здесь.
Оригинал