如果要进行放大,可以使用Terraform

你好。我是一名前端初学者。
最近,我试图使用Amplify和Appsync来学习Vue.js。在此过程中,我觉得使用Terraform比使用amplify-cli和CloudFormation来构建AWS环境更易管理且更灵活,所以我想向大家报告一下。
由于Terraform、Vue.js和Amplify都是我第一次使用,所以可能会有些问题。如果你能给我一些建议,我会非常高兴。

为什么选择Terraform?

懂得Amplify且熟悉CloudFormation的人,我认为使用amplify-cli会更方便。可能没有特别需要使用Terraform的理由。

在这次使用Terraform的动机是,当我在S3的触发器上创建了Lambda函数时,想要将环境变量传递给源代码,但不知道如何配置。还有,想将GraphQL配置为IAM认证,只允许特定的查询进行访问,但也不知道如何配置。而在Terraform中,只需阅读文档,就可以灵活地进行这些配置。

以下是我认为Terraform更易于使用的三个方面,其中后两个尤为重要。

没有超过自己写的内容设定

使用amplify-cli时,根据应用的不同可能会创建不必要的资源,以及使用自动生成的名称。
当然,在使用Terraform时,您可以自己命名资源,并且不会创建超过您配置的资源数量,因此更容易理解正在进行的操作。
这可能是一个初学者的问题。

将资源更改编码化的循环变得简单。

amplify-cli会通过添加/更新/推送等命令来更新资源,但要持续修改/管理创建的资源,很难理解在哪里修改也很困难。
在Terraform中,即使在AWS控制台更改了内容,也可以通过导入->计划->应用的重复过程,持续将其编码化,这是个好处。

可以将数据与设置分离并进行编码。

amplify-cli将输出一个名为”amplify/backend/awscloudformation/nested-cloudformation-stack.yml”的堆栈定义文件,其中含有ARN和帐户ID等信息,这使得难以共享所创建的工作。相比之下,Terraform可以通过tfvars和tfstate来隐藏不想展示的信息,并且仅通过tf文件来共享定义。

使用Terraform创建Amplify环境。

那么,我想用Terraform构建aws-samples/aws-amplify-vue的环境,并确保它能够正常工作。
我们不会添加不需要使用的资源,这些资源是由amplify-cli生成的。
我已经将创建的内容放在了github上。
可以通过git clone -> terraform init & apply -> npm install & start来使用它,希望这对你有所帮助。

准备筹备

首先,创建必要的文件并配置Terraform的使用。请将tfvars和ftstate添加到.gitignore中。


mkdir terraform
touch terraform/main.tf terraform/aws-exports.tf terraform/terraform.tfvars
cat <<EOF >> .gitignore
terraform/.terraform
terraform/terraform.tfvars
terraform/terraform.tfstate*
EOF
terraform init

编辑以下创建的文件。


variable "app_name" {
  type    = "string"
  default = "amplify_terraform_example"
}

variable "app_env" {
  type    = "string"
  default = "dev"
}

variable "aws_profile" {}
variable "aws_default_region" {}

provider "aws" {
  profile = "${var.aws_profile}"
  region  = "${var.aws_default_region}"
}

data "aws_caller_identity" "current" {}

main.tf负责执行应用程序名称、环境名称和AWS提供程序的初始化。

由于AWS帐户是通过配置文件指定的,所以请在使用aws-cli之前提前创建配置文件,并将其与terraform.tfvars文件中的区域设置一起配置。

resource "local_file" "aws_exports_js" {
  filename = "../src/aws-exports.js"
  content  = <<EOF
const awsmobile = {
    "aws_project_region": "${var.aws_default_region}",
};
export default awsmobile;
EOF
}

aws-exports.tf是用于生成Amplify应用程序的配置文件的文件。目前仅导出区域信息。

如果可能的话,先使用terraform plan确认一下是否没有错误。

进行初始设定

在这里,我们将进行相当于amplify init的设置。我们将创建经过身份验证和未经身份验证的IAM角色。虽然amplify-cli会创建名为”deployment”的CFn S3存储桶,但我们不会使用它,所以不创建。


resource "aws_iam_role" "authenticated_user" {
  name               = "${var.app_name}_${var.app_env}_authed_user_role"
  assume_role_policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "cognito-identity.amazonaws.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "ForAnyValue:StringLike": {
                    "cognito-identity.amazonaws.com:amr": "authenticated"
                }
            }
        }
    ]
}
EOF
}

resource "aws_iam_role" "unauthenticated_user" {
  name = "${var.app_name}_${var.app_env}_unauthed_user_role"
  assume_role_policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "cognito-identity.amazonaws.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "ForAnyValue:StringLike": {
                    "cognito-identity.amazonaws.com:amr": "unauthenticated"
                }
            }
        }
    ]
}
EOF
}

验证

进行与amplify add auth相当的设置。
在认证方面,我们将添加cognito用户池和身份池的资源。在身份池中,我们将设置以前面设置的IAM角色的ID分配给经过身份验证的用户和未经身份验证的用户。
在这里,我们不使用amplify-cli添加SNS主题或Lambda函数,因此下面没有进行描述。

请修改 aws-exports.js 文件,以便导出 cognito 的配置。


variable "aws_cognito_user_pool_name" {
  type    = "string"
  default = "user_pool"
}

variable "aws_cognito_user_pool_client_web_name" {
  type    = "string"
  default = "user_pool_client_web"
}

variable "aws_cognito_user_pool_client_app_name" {
  type    = "string"
  default = "user_pool_client_app"
}

variable "aws_cognito_identity_pool_name" {
  type    = "string"
  default = "identity_pool"
}

resource "aws_cognito_user_pool" "userpool" {
  name = "${var.app_name}_${var.app_env}_${var.aws_cognito_user_pool_name}"
  auto_verified_attributes = [
    "email"
  ]

  password_policy {
    minimum_length    = 8
    require_lowercase = true
    require_numbers   = true
    require_symbols   = false
    require_uppercase = false
  }
}

resource "aws_cognito_user_pool_client" "webclient" {
  name            = "${var.app_name}_${var.app_env}_${var.aws_cognito_user_pool_client_web_name}"
  user_pool_id    = "${aws_cognito_user_pool.userpool.id}"
  generate_secret = false
}

resource "aws_cognito_user_pool_client" "appclient" {
  name            = "${var.app_name}_${var.app_env}_${var.aws_cognito_user_pool_client_app_name}"
  user_pool_id    = "${aws_cognito_user_pool.userpool.id}"
  generate_secret = false
}

resource "aws_cognito_identity_pool" "idpool" {
  identity_pool_name               = "${var.app_name}_${var.app_env}_${var.aws_cognito_identity_pool_name}"
  allow_unauthenticated_identities = true
  cognito_identity_providers {
    client_id               = "${aws_cognito_user_pool_client.webclient.id}"
    provider_name           = "${aws_cognito_user_pool.userpool.endpoint}"
    server_side_token_check = false
  }
  cognito_identity_providers {
    client_id               = "${aws_cognito_user_pool_client.appclient.id}"
    provider_name           = "${aws_cognito_user_pool.userpool.endpoint}"
    server_side_token_check = false
  }
}

resource "aws_cognito_identity_pool_roles_attachment" "user_role_attachment" {
  identity_pool_id = "${aws_cognito_identity_pool.idpool.id}"
  roles = {
    "authenticated"   = "${aws_iam_role.authenticated_user.arn}"
    "unauthenticated" = "${aws_iam_role.unauthenticated_user.arn}"
  }
}

如果可以的话,请执行一次 terraform apply。当您访问应用程序时,执行 npm start,我认为您将能够在已创建的用户池中进行注册和登录。

存储空间

接下来,您需要进行类似于”amplify add storage”的设置。首先,需要创建一个存储桶用于存储。


variable "aws_s3_bucket_storage_name" {
  type = "string"
  default = "storage-bucket"
}

resource "aws_s3_bucket" "storage_bucket" {
  bucket = "${replace(var.app_name, "_", "-")}-${var.app_env}-${var.aws_s3_bucket_storage_name}"
  cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["GET", "HEAD", "PUT", "POST", "DELETE"]
    allowed_origins = ["*"]
    expose_headers = [
      "x-amz-server-side-encryption",
      "x-amz-request-id",
      "x-amz-id-2",
      "ETag"
    ]
    max_age_seconds = 3000
  }
}

然后,我们将授予IAM角色对该存储桶的权限。根据示例应用的实现方式,我们需要仅允许经过身份验证的用户访问。如果要为访客提供访问权限,则只需在此处将策略连接到未经身份验证的用户即可。

这个在amplify-cli里面很容易做到,但是在Terraform里却相当麻烦。我认为这个IAM的设置是Amplify的核心部分,如果自己去设置的话,理解会更加深入。


resource "aws_iam_role" "authenticated_user" {
  name               = "${var.app_name}_${var.app_env}_authed_user_role"
  assume_role_policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "cognito-identity.amazonaws.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "ForAnyValue:StringLike": {
                    "cognito-identity.amazonaws.com:amr": "authenticated"
                }
            }
        }
    ]
}
EOF
}

resource "aws_iam_role" "unauthenticated_user" {
  name               = "${var.app_name}_${var.app_env}_unauthed_user_role"
  assume_role_policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "cognito-identity.amazonaws.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "ForAnyValue:StringLike": {
                    "cognito-identity.amazonaws.com:amr": "unauthenticated"
                }
            }
        }
    ]
}
EOF
}

resource "aws_iam_policy" "storage_upload" {
  name   = "${var.app_name}_${var.app_env}_storage_upload_policy"
  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:PutObject"
            ],
            "Resource": [
                "${aws_s3_bucket.storage_bucket.arn}/uploads/*"
            ],
            "Effect": "Allow"
        }
    ]
}
EOF
}

resource "aws_iam_policy_attachment" "attach_storage_upload" {
  name       = "attach_storage_upload"
  roles      = ["${aws_iam_role.authenticated_user.name}"]
  policy_arn = "${aws_iam_policy.storage_upload.arn}"
}

resource "aws_iam_policy" "storage_read" {
  name   = "${var.app_name}_${var.app_env}_storage_read_policy"
  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "${aws_s3_bucket.storage_bucket.arn}/protected/*"
            ],
            "Effect": "Allow"
        },
        {
            "Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "public/",
                        "public/*",
                        "protected/",
                        "protected/*",
                        "private/$${cognito-identity.amazonaws.com:sub}/",
                        "private/$${cognito-identity.amazonaws.com:sub}/*"
                    ]
                }
            },
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "${aws_s3_bucket.storage_bucket.arn}"
            ],
            "Effect": "Allow"
        }
    ]
}
EOF
}

resource "aws_iam_policy_attachment" "attach_storage_read" {
  name       = "attach_storage_read"
  roles      = ["${aws_iam_role.authenticated_user.name}"]
  policy_arn = "${aws_iam_policy.storage_read.arn}"
}

resource "aws_iam_policy" "storage_write_public" {
  name   = "${var.app_name}_${var.app_env}_storage_write_public_policy"
  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "${aws_s3_bucket.storage_bucket.arn}/public/*"
            ],
            "Effect": "Allow"
        }
    ]
}
EOF
}

resource "aws_iam_policy_attachment" "attach_storage_write_public" {
  name       = "attach_storage_write_public"
  roles      = ["${aws_iam_role.authenticated_user.name}"]
  policy_arn = "${aws_iam_policy.storage_write_public.arn}"
}

resource "aws_iam_policy" "storage_write_protected" {
  name   = "${var.app_name}_${var.app_env}_storage_write_protected_policy"
  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "${aws_s3_bucket.storage_bucket.arn}/protected/$${cognito-identity.amazonaws.com:sub}/*"
            ],
            "Effect": "Allow"
        }
    ]
}
EOF
}

resource "aws_iam_policy_attachment" "attach_storage_write_protected" {
  name       = "attach_storage_write_protected"
  roles      = ["${aws_iam_role.authenticated_user.name}"]
  policy_arn = "${aws_iam_policy.storage_write_protected.arn}"
}

resource "aws_iam_policy" "storage_write_private" {
  name   = "${var.app_name}_${var.app_env}_storage_write_private_policy"
  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "${aws_s3_bucket.storage_bucket.arn}/private/$${cognito-identity.amazonaws.com:sub}/*"
            ],
            "Effect": "Allow"
        }
    ]
}
EOF
}

resource "aws_iam_policy_attachment" "attach_storage_write_private" {
  name       = "attach_storage_write_private"
  roles      = ["${aws_iam_role.authenticated_user.name}"]
  policy_arn = "${aws_iam_policy.storage_write_private.arn}"
}

我们将在aws-exports.js文件中设置导出存储桶名称等信息。

当你登录后进入个人资料页面时,我认为你可以上传自己的头像。这些头像将会公开上传,因此应该可以访问其他账户的头像。

Appsync GraphQL API -> Appsync 图形查询API

最終添加功能是为应用程序添加Appsync。这相当于运行amplify add api命令。
我们将创建DynamoDB表,并将其作为数据源配置到Appsync上的GraphQL API中。

在这里设置GraphQL的模式和解析器。关于内容我不进行解释,但是模式和解析器在terraform/appsync/graphql的文件夹下以文件形式进行配置,您可以自由定制。使用amplify-cli时,会自动生成订阅等模式,但是现在不需要,所以我没有添加。


variable "aws_dynamodb_table_todos_name" {
  type    = "string"
  default = "TodoTable"
}

variable "aws_iam_role_appsync_todos_api_name" {
  type        = "string"
  default     = "appsync_todos_api_role"
  description = "Appsync execution role"
}

variable "aws_iam_role_policy_appsync_todos_api_name" {
  type        = "string"
  default     = "appsync_todos_api_role_policy"
  description = "Appsync execution role policy"
}

variable "aws_appsync_datasource_todos_name" {
  type    = "string"
  default = "TodoDatasource"
}

variable "aws_appsync_graphql_api_todos_name" {
  type    = "string"
  default = "todos_api"
}

data "local_file" "graphql_todos_api_schema" {
  filename = "appsync/graphql/schema.graphql"
}

data "local_file" "graphql_resolver_create_todo" {
  filename = "appsync/graphql/resolvers/createTodo.vm"
}

data "local_file" "graphql_resolver_update_todo" {
  filename = "appsync/graphql/resolvers/updateTodo.vm"
}

data "local_file" "graphql_resolver_delete_todo" {
  filename = "appsync/graphql/resolvers/deleteTodo.vm"
}

data "local_file" "graphql_resolver_get_todo" {
  filename = "appsync/graphql/resolvers/getTodo.vm"
}

data "local_file" "graphql_resolver_list_todos" {
  filename = "appsync/graphql/resolvers/listTodos.vm"
}

data "local_file" "graphql_resolver_response" {
  filename = "appsync/graphql/resolvers/response.vm"
}

resource "aws_dynamodb_table" "todos_table" {
  name         = "${var.app_name}_${var.app_env}_${var.aws_dynamodb_table_todos_name}"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "id"

  attribute {
    name = "id"
    type = "S"
  }
}

resource "aws_iam_role" "appsync_todos_api_role" {
  name               = "${var.aws_iam_role_appsync_todos_api_name}"
  assume_role_policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": "sts:AssumeRole",
            "Principal": {
                "Service": "appsync.amazonaws.com"
            },
            "Effect": "Allow"
        }
    ]
}
EOF
}

resource "aws_iam_role_policy" "appsync_todos_api_role_policy" {
  name = "${var.aws_iam_role_policy_appsync_todos_api_name}"
  role = "${aws_iam_role.appsync_todos_api_role.id}"
  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "dynamodb:*"
            ],
            "Effect": "Allow",
            "Resource": [
                "${aws_dynamodb_table.todos_table.arn}",
                "${aws_dynamodb_table.todos_table.arn}/*"
            ]
        }
    ]
}
EOF
}

resource "aws_appsync_graphql_api" "todos_api" {
  authentication_type = "AMAZON_COGNITO_USER_POOLS"
  name                = "${var.app_name}_${var.app_env}_${var.aws_appsync_graphql_api_todos_name}"
  schema              = "${data.local_file.graphql_todos_api_schema.content}"
  user_pool_config {
    aws_region     = "${var.aws_default_region}"
    default_action = "ALLOW"
    user_pool_id   = "${aws_cognito_user_pool.userpool.id}"
  }
}

resource "aws_appsync_datasource" "todos_datasource" {
    api_id           = "${aws_appsync_graphql_api.todos_api.id}"
    name             = "${var.aws_appsync_datasource_todos_name}"
    service_role_arn = "${aws_iam_role.appsync_todos_api_role.arn}"
    type             = "AMAZON_DYNAMODB"
    dynamodb_config {
        table_name = "${aws_dynamodb_table.todos_table.name}"
    }
}

resource "aws_appsync_resolver" "create_todo" {
  api_id            = "${aws_appsync_graphql_api.todos_api.id}"
  field             = "createTodo"
  type              = "Mutation"
  data_source       = "${aws_appsync_datasource.todos_datasource.name}"
  request_template  = "${data.local_file.graphql_resolver_create_todo.content}"
  response_template = "${data.local_file.graphql_resolver_response.content}"
}

resource "aws_appsync_resolver" "update_todo" {
  api_id            = "${aws_appsync_graphql_api.todos_api.id}"
  field             = "updateTodo"
  type              = "Mutation"
  data_source       = "${aws_appsync_datasource.todos_datasource.name}"
  request_template  = "${data.local_file.graphql_resolver_update_todo.content}"
  response_template = "${data.local_file.graphql_resolver_response.content}"
}

resource "aws_appsync_resolver" "delete_todo" {
  api_id            = "${aws_appsync_graphql_api.todos_api.id}"
  field             = "deleteTodo"
  type              = "Mutation"
  data_source       = "${aws_appsync_datasource.todos_datasource.name}"
  request_template  = "${data.local_file.graphql_resolver_delete_todo.content}"
  response_template = "${data.local_file.graphql_resolver_response.content}"
}

resource "aws_appsync_resolver" "get_todo" {
  api_id            = "${aws_appsync_graphql_api.todos_api.id}"
  field             = "getTodo"
  type              = "Query"
  data_source       = "${aws_appsync_datasource.todos_datasource.name}"
  request_template  = "${data.local_file.graphql_resolver_get_todo.content}"
  response_template = "${data.local_file.graphql_resolver_response.content}"
}

resource "aws_appsync_resolver" "list_todos" {
  api_id            = "${aws_appsync_graphql_api.todos_api.id}"
  field             = "listTodos"
  type              = "Query"
  data_source       = "${aws_appsync_datasource.todos_datasource.name}"
  request_template  = "${data.local_file.graphql_resolver_list_todos.content}"
  response_template = "${data.local_file.graphql_resolver_response.content}"
}

请确保在 aws-exports.js 文件中设置 GraphQL 的端点等。
以上步骤完成后,执行 terraform apply 后就可以执行 Todo 的添加/完成/删除操作了。

总结

我已经使用Terraform创建了一个样本实施环境。虽然还有一些主机托管的配置等设置,但是只要能够完成到这一步,我觉得那也不是太困难,就不赘述了。
使用Terraform逐一配置每个资源加深了对Amplify的理解。希望能够进一步加深理解,甚至能够为amplify-cli做出贡献。

广告
将在 10 秒后关闭
bannerAds