Ошибка создания цели автомасштабирования приложения на AWS при использовании Terraform - определение `scalable_resource` для настраиваемого ʻaws_appautoscaling_target`
Цель
Я реализую решение для автоматического масштабирования потоков данных Kinesis.
Одно возможное решение, которому я следую, хорошо задокументировано в https://github.com/aws-samples/aws-application-auto-scaling-kinesis. Однако в примере кода используется шаблон yaml облачной информации. Я хочу определить то же самое, используя terraform.
История до сих пор
При попытке создать цель масштабирования для my_custom_resource
,
resource "aws_appautoscaling_target" "my_custom_resource" {
resource_id = "https://${aws_api_gateway_rest_api.my_api.id}.execute-api.${var.region}.amazonaws.com/prod/scalableTargetDimensions/${var.stream}"
scalable_dimension = "custom-resource:ResourceType:Property"
service_namespace = "custom-resource"
}
Все атрибуты созданы в соответствии с документами AWS Auto-Scaling.
Тот же ресурс создается с помощью CloudFormation в связанном репозитории AWS:
KinesisAutoScaling:
Type: AWS::ApplicationAutoScaling::ScalableTarget
DependsOn: LambdaScaler
Properties:
ResourceId: !Sub https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/prod/scalableTargetDimensions/${MyKinesisStream}
ScalableDimension: 'custom-resource:ResourceType:Property'
ServiceNamespace: custom-resource
Примечание. Неактуальные атрибуты опущены для краткости.
Проблема
terraform apply
выдает следующую ошибку:
Ошибка: ошибка при создании цели автомасштабирования приложения: ValidationException: Ошибка проверки ресурса: https://k5df89sd23.execute-api.us-west-1.amazonaws.com/prod/scalableTargetDimensions/my-test-stream, масштабируемое измерение: custom-resource:ResourceType:Property. Причина: масштабируемый ресурс не найден в строке 9 application-autoscaling.tf в ресурсе "aws_appautoscaling_target" "my_custom_resource": 9: ресурс "aws_appautoscaling_target" "my_custom_resource" {
Что может быть не так в определении терраформы?
Я знаю, что Terraform поддерживает шаблоны CloudFormation, используя aws_cloudformation_stack
- обходной путь, которого я искренне хочу избежать.
1 ответ
ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я знаю, что вопрос был задан некоторое время назад, но я все равно отвечаю на тот случай, если это может помочь кому-то еще... Это определенно помогло бы мне.
Предполагая, что вы пытаетесь воспроизвести содержимое , вот что, по моему мнению, может быть неправильным:
Конечно часть ответа:
aws_appautoscaling_target.my_custom_resource.resource_id
Если вы создали
aws_api_gateway_deployment
ресурс для воспроизведения части MyApi из aws-sample, тогда вам повезло!
вы можете использовать это:
resource "aws_appautoscaling_target" "my_custom_resource" {
# ...
resource_id = "${aws_api_gateway_deployment.gateway.invoke_url}/scalableTargetDimensions/${var.stream}"
# ...
Если вам нужны подробности о том, как заставить это ^ работать, смотрите ниже...
Но имейте в виду, что этого может быть недостаточно!
Вероятно, это тоже часть решения: внутренние ссылки и разрешения, необходимые для создания
всегда предполагая, что вы следуете одному и тому ссылки, которую вы указали в вопросеже примеру и что вы реализовали большую часть его с ...
также
ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: я не уверен в точной причине остальной части этого ответа. Подозреваю, что это как-то связано с требованиями внутреннего API сервиса AWS Auto Scaling.
Вкратце: все должно быть подключено и работать, прежде чем вы сможете зарегистрировать цель автомасштабирования.
Эта часть: из
README.md
этого другого проекта, который охватывает интеграцию, которую вы пытаетесь достичь, намекает на это.
Длинная версия; Что мне пришлось исправить в своей реализации:
- Формат лямбда-ответа
- Настройки шлюза API и проводка
- Правильные разрешения IAM
- Lambda и APIGateway в хорошем рабочем состоянии
1. Убедитесь, что у вас правильный лямбда-ответ
Формат возвращаемого значения лямбды имеет решающее значение для создания
aws_appautoscaling_target
.
Это должно быть ТОЧНО :
returningJson = {
"actualCapacity": float(actualCapacity),
"desiredCapacity": float(desiredCapacity),
"dimensionName": resourceName,
"resourceName": resourceName,
"scalableTargetDimensionId": resourceName,
"scalingStatus": scalingStatus,
"version": "MyVersion"
}
try:
returningJson['failureReason'] = failureReason
except:
pass
(именно так это определено в образце)...
В моей реализации я поиграл с этим (до того, как все было развернуто и сделано), думая, что смогу получить больше данных из вызова, для метрик и мониторинга...
Оказывается что в конце концов, когда все остальное было сделано, все, что мне нужно было сделать, это восстановить функцию возврата, и все это успешно подключилось.
2. Настройки и проводка шлюза API
Эта часть вызвала у меня затруднения. Я думаю, что это должно быть сделано правильно, чтобы API автоматического масштабирования подключался и находил цель, чтобы он мог ее зарегистрировать (это то, что делает ресурс, который вы пытаетесь создать).
Это определение API является допустимым
openapi.yaml
определение.
Я предлагаю поместить это в файл, подобный этому:
openapi.yaml.template
swagger: '2.0'
info:
title: "${NAME}"
paths:
'/scalableTargetDimensions/{scalableTargetDimensionId}':
get:
tags:
- ScalableTargets
x-tags:
- tag: ScalableTargets
security:
- sigv4: []
x-amazon-apigateway-any-method:
produces:
- application/json
consumes:
- application/json
x-amazon-apigateway-integration:
httpMethod: POST
type: aws_proxy
uri: ${INTEGRATION_URI}
responses: {}
patch:
tags:
- ScalableTargets
x-tags:
- tag: ScalableTargets
security:
- sigv4: []
x-amazon-apigateway-any-method:
security:
- sigv4: []
produces:
- application/json
consumes:
- application/json
x-amazon-apigateway-integration:
httpMethod: POST
type: aws_proxy
uri: ${INTEGRATION_URI}
responses: {}
securityDefinitions:
sigv4:
type: apiKey
name: Authorization
in: header
x-amazon-apigateway-authtype: awsSigv4
И вы можете использовать его следующим образом:
# API Gateway
resource "aws_api_gateway_rest_api" "gateway" {
name = var.rest_api_name
body = templatefile("${path.module}/openapi.yaml.template",
{
NAME = var.rest_api_name,
INTEGRATION_URI = var.integration_uri
}
)
}
resource "aws_api_gateway_deployment" "gateway" {
depends_on = [
aws_api_gateway_rest_api.gateway,
]
lifecycle {
create_before_destroy = true
}
rest_api_id = aws_api_gateway_rest_api.gateway.id
stage_name = var.stage_name
}
3. Правильные разрешения IAM
Разрешения, определенные в
cloudformation
шаблоны и те, которые вы можете создать с помощью
terraform
не являются точным совпадением и, похоже, нуждаются в некоторой настройке, чтобы интеграция заработала... (Я подозреваю, что это связано с некоторой магией AWS, но в целом я считаю, что перенос в терраформу относительно прост)
Итак, вот
role
а также
policies
В итоге я создал:
# Lambda
resource "aws_lambda_function" "lambda" {
# ...
role = aws_iam_role.kinesis_autoscaler_lambda_role.arn
# ...
}
resource "aws_iam_role" "kinesis_autoscaler_lambda_role" {
name = "${var.env}-kinesis-scaler-lambda-role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_lambda_permission" "kinesis_api" {
statement_id = "AllowKinesisAPIInvoke"
function_name = aws_lambda_function.lambda.function_name
action = "lambda:InvokeFunction"
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_deployment.gateway.execution_arn}/GET/scalableTargetDimensions/{scalableTargetDimensionId}"
}
resource "aws_lambda_permission" "kinesis_api_patch" {
statement_id = "AllowKinesisAPIPatchInvoke"
function_name = aws_lambda_function.lambda.function_name
action = "lambda:InvokeFunction"
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_deployment.gateway.execution_arn}/PATCH/scalableTargetDimensions/{scalableTargetDimensionId}"
}
# Permissions
resource "aws_iam_policy" "lambda_access_stream" {
name = "${var.stream_name}-access-stream-policy"
policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "KinesisConsumerAccess",
"Effect": "Allow",
"Action": [
"kinesis:DescribeStreamConsumer"
],
"Resource": "${aws_kinesis_stream.stream.arn}/consumer/*:*"
},
{
"Sid": "KinesisStreamAccess",
"Effect": "Allow",
"Action": [
"kinesis:DescribeLimits",
"kinesis:DescribeStream",
"kinesis:DescribeStreamConsumer",
"kinesis:DescribeStreamSummary",
"kinesis:UpdateShardCount"
],
"Resource": "${aws_kinesis_stream.stream.arn}"
},
{
"Sid": "SSMParameterStoreGet",
"Effect": "Allow",
"Action": [
"ssm:GetParameter"
],
"Resource": [
"${aws_ssm_parameter.number_of_shards.arn}",
]
},
{
"Sid": "SSMParameterStorePut",
"Effect": "Allow",
"Action": [
"ssm:PutParameter"
],
"Resource": [
"${aws_ssm_parameter.number_of_shards.arn}"
]
}
]
}
POLICY
}
# I ended up splitting the policies for `module` reasons...
resource "aws_iam_policy_attachment" "attach_lambda_stream_access" {
name = "${aws_iam_role.kinesis_autoscaler_lambda_role.name}_attach_lambda_stream_access"
roles = [
aws_iam_role.kinesis_autoscaler_lambda_role.name
]
policy_arn = aws_iam_policy.lambda_access_stream.arn
}
Это последнее является важным. Это результат последних настроек, которые я сделал, чтобы лямбда заработала.
resource "aws_iam_policy" "lambda_access_scaling" {
name = "${var.stream_name}-lambda-access-scaling-policy"
policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "FindScalingPolicyARNAndAlarms",
"Effect": "Allow",
"Action": [
"application-autoscaling:DescribeScalingPolicies",
"cloudwatch:DescribeAlarms"
],
"Resource": "*"
},
{
"Sid": "UpdateAlarms",
"Effect": "Allow",
"Action": [
"cloudwatch:PutMetricAlarm",
"cloudwatch:DeleteAlarms"
],
"Resource": [
"${aws_cloudwatch_metric_alarm.alarm_out.arn}",
"${aws_cloudwatch_metric_alarm.alarm_in.arn}"
]
}
]
}
POLICY
}
resource "aws_iam_policy_attachment" "attach_scaling_access" {
name = "${aws_iam_role.kinesis_autoscaler_lambda_role.name}_attach_scaling_access"
roles = [
aws_iam_role.kinesis_autoscaler_lambda_role.name
]
policy_arn = aws_iam_policy.lambda_access_scaling.arn
}
Кроме того, вы хотите иметь правильные разрешения для
custom_appautoscaling
...
# For reference:
resource "aws_appautoscaling_target" "kinesis_stream" {
min_capacity = var.min_number_of_shard
max_capacity = var.max_number_of_shard
resource_id = "${aws_api_gateway_deployment.gateway.invoke_url}/scalableTargetDimensions/${var.stream_name}"
role_arn = aws_iam_role.custom_appautoscaling_service_role.arn
scalable_dimension = "custom-resource:ResourceType:Property"
service_namespace = "custom-resource"
depends_on = [
aws_iam_policy_attachment.attach_base_policy,
]
lifecycle {
ignore_changes = [
# This is because the "assume_role_policy" becomes the actual
# Role "AWSServiceRoleForApplicationAutoScaling_CustomResource"
# at runtime and is always attemted to be recreated
role_arn,
]
}
}
# Actual policies:
resource "aws_iam_role" "custom_appautoscaling_service_role" {
name = "${var.stream_name}-assume-custom-resource"
assume_role_policy = <<-EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "custom-resource.application-autoscaling.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_iam_policy" "base_policy" {
name = "${var.stream_name}_base_policy"
policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DescribeAlarms",
"Effect": "Allow",
"Action": [
"cloudwatch:DescribeAlarms"
],
"Resource": "*"
},
{
"Sid": "InvokeApiGateway",
"Effect": "Allow",
"Action": [
"execute-api:Invoke*"
],
"Resource": [
"${aws_api_gateway_deployment.gateway.execution_arn}/scalableTargetDimensions/${var.stream_name}"
]
}
]
}
POLICY
}
resource "aws_iam_policy_attachment" "attach_base_policy" {
name = "${var.stream_name}_attach_base_policy"
roles = [
aws_iam_role.custom_appautoscaling_service_role.name
]
policy_arn = aws_iam_policy.base_policy.arn
}
resource "aws_iam_policy" "alarms_modification" {
name = "${var.stream_name}_alarms_modification"
policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "UpdateAlarms",
"Effect": "Allow",
"Action": [
"cloudwatch:PutMetricAlarm",
"cloudwatch:DeleteAlarms"
],
"Resource": [
"${aws_cloudwatch_metric_alarm.alarm_out.arn}",
"${aws_cloudwatch_metric_alarm.alarm_in.arn}"
]
}
]
}
POLICY
}
resource "aws_iam_policy_attachment" "attach_alarms_modification" {
name = "${var.stream_name}_attach_alarms_modification"
roles = [
aws_iam_role.custom_appautoscaling_service_role.name
]
policy_arn = aws_iam_policy.alarms_modification.arn
}
4. Lambda и APIGateway в хорошем рабочем состоянии
Наконец, если после всего этого все еще не работает, вы можете захотеть устранить неполадки с вашей лямбдой и APIGateway... В
итоге я использовал
apigateway-aws-proxy
Шаблон события в лямбда-консоли. Немного редактируем поля, чтобы это выглядело так:
{
"body": {
"desiredCapacity": "1"
},
"resource": "/{proxy+}",
"path": "/scalableTargetDimensions/my-custom-stream",
"httpMethod": "PATCH",
...
"requestContext" {
...
"path": "/<STAGE_NAME_SEE_API_GATEWAY_DEPLOYMENT_RESOURCE>/scalableTargetDimensions/my-custom-stream",
"resourcePath": "/{proxy+}",
"httpMethod": "PATCH",
}
Также для этой части я создал
base64encoded
body (потому что это то, как он передается через APIGateway, я думаю?) ...
В любом случае, я немного подправил лямбду, чтобы она принимала оба, таким образом можно узнать, действительно ли лямбда работает правильно.
{
"body": "eyJkZXNpcmVkQ2FwYWNpdHkiOiIyIn0=",
"resource": "/{proxy+}",
"path": "/scalableTargetDimensions/my-custom-stream",
"httpMethod": "PATCH",
"isBase64Encoded": true,
...
При этом вы сможете устранить неполадки лямбды, убедившись, что все разрешения предоставлены правильно.
Для устранения неполадок APIGateway вы всегда можете использовать postman. Он хорошо обрабатывает аутентификацию в AWS, поэтому, если у вас есть учетные данные с адекватным доступом к созданному вами ресурсу, вы сможете выполнить некоторые действия.
GET
а также
PATCH
чтобы вручную запустить
APIGateway
и протестируйте эту часть интеграции.