如果要进行放大,可以使用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做出贡献。