

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 使用 Terraform 执行 Amazon Redshift SQL 查询
<a name="execute-redshift-sql-queries-using-terraform"></a>

*Sylvia Qi 和 Aditya Ambati， Amazon Web Services*

## Summary
<a name="execute-redshift-sql-queries-using-terraform-summary"></a>

使用基础设施即代码 (IaC) 来部署和管理 Amazon Redshift 是一种普遍的做法。 DevOpsIaC 有助于部署和配置各种 Amazon Redshift 资源，例如集群、快照和参数组。但是，IaC 并未扩展到管理数据库资源，例如表、架构、视图和存储过程。这些数据库元素通过 SQL 查询进行管理，并且不受 IaC 工具支持，无法直接进行处理。尽管有用于管理这些资源的解决方案和工具，但您可能不希望在技术堆栈中引入其他工具。

此模式概述了一种使用 Terraform 部署 Amazon Redshift 数据库资源（包括表、架构、视图和存储过程）的方法。该模式区分了两种类型的 SQL 查询：
+ **不可重复查询** – 这些查询在最初的 Amazon Redshift 部署期间执行一次，以建立基本的数据库组件。
+ **可重复查询** – 这些查询是不可变的，可以在不影响数据库的情况下重新运行。该解决方案使用 Terraform 来监控可重复查询中的变化并相应地应用它们。

有关更多详细信息，请参阅[其他信息](#execute-redshift-sql-queries-using-terraform-additional)中的*解决方案演练*。

## 先决条件和限制
<a name="execute-redshift-sql-queries-using-terraform-prereqs"></a>

**先决条件**

您必须激活 AWS 账户 并在部署计算机上安装以下内容：
+ [AWS Command Line Interface](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) (AWS CLI)
+ [AWS CLI 配置](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)了亚马逊 Redshi read/write ft 权限的个人资料
+ [Terraform](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) 版本 1.6.2 或更高版本
+ [Python3](https://www.python.org/downloads/)
+ [Boto3](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html)

**限制**
+ 此解决方案支持单个 Amazon Redshift 数据库，因为 Terraform 只允许在创建集群期间创建一个数据库。
+ 此模式不包括在应用对可重复查询的更改之前验证该更改的测试。我们建议您采用此类测试以增强可靠性。
+ 为了说明该解决方案，此模式提供了一个使用本地 Terraform 状态文件的示例 `redshift.tf` 文件。但是，对于生产环境，我们强烈建议您使用带有锁定机制的远程状态文件，以增强稳定性和协作性。
+ 有些 AWS 服务 并非全部可用 AWS 区域。有关区域可用性，请参阅[按区域划分的AWS 服务](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/)。有关特定端点，请参阅[服务端点和配额](https://docs.aws.amazon.com/general/latest/gr/aws-service-information.html)，然后选择相应服务的链接。

**产品版本**

该解决方案是在 [Amazon Redshift 补丁 179](https://docs.aws.amazon.com/redshift/latest/mgmt/cluster-versions.html#cluster-version-179) 上开发和测试的。

**代码存储库**

此模式的代码可在 GitHub [amazon-redshift-sql-deploy-terraform](https://github.com/aws-samples/amazon-redshift-sql-deploy-terraform) 存储库中找到。

## 架构
<a name="execute-redshift-sql-queries-using-terraform-architecture"></a>

下图阐明了 Terraform 如何通过处理不可重复和可重复的 SQL 查询来管理 Amazon Redshift 数据库资源。

![\[Terraform 使用 SQL 查询管理 Amazon Redshift 数据库资源的流程。\]](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/patterns/images/pattern-img/0f4467ac-761b-4b6b-a32f-e18a2ca2245d/images/3b6ff9e8-e3d1-48ed-9fa1-4b14f7d3d65b.png)


图中显示以下步骤：

1. 在最初的 Amazon Redshift 集群部署期间，Terraform 会应用不可重复的 SQL 查询。

1. 开发人员提交对可重复的 SQL 查询的更改。

1. Terraform 监控可重复的 SQL 查询中的变化。

1. Terraform 将可重复的 SQL 查询应用到 Amazon Redshift 数据库。

此模式提供的解决方案是基于[适用于 Amazon Redshift 的 Terraform 模块](https://registry.terraform.io/modules/terraform-aws-modules/redshift/aws/latest)构建的。Terraform 模块预调配了 Amazon Redshift 集群和数据库。为了增强该模块，我们使用了`terraform_data`资源，这些资源调用自定义 Python 脚本来使用 Amazon [ExecuteStatement](https://docs.aws.amazon.com/redshift-data/latest/APIReference/API_ExecuteStatement.html)Redshift API 操作执行 SQL 查询。因此，该模块可以执行以下操作：
+ 预调配数据库后，使用 SQL 查询部署任意数量的数据库资源。
+ 持续监控可重复的 SQL 查询中的更改，并使用 Terraform 应用这些更改。

有关更多详细信息，请参阅[其他信息](#execute-redshift-sql-queries-using-terraform-additional)中的*解决方案演练*。

## 工具
<a name="execute-redshift-sql-queries-using-terraform-tools"></a>

**AWS 服务**
+ [Amazon Redshift](https://docs.aws.amazon.com/redshift/latest/mgmt/welcome.html) 是 AWS 云中的一项完全托管式 PB 级数据仓库服务。

**其他工具**
+ [Terraform](https://www.terraform.io/) 是一款基础设施即代码 (IaC) 工具 HashiCorp ，可帮助您创建和管理云和本地资源。
+ [Python](https://www.python.org/) 是一种通用的编程语言，在此模式中用于执行 SQL 查询。

## 最佳实践
<a name="execute-redshift-sql-queries-using-terraform-best-practices"></a>
+ [亚马逊 Redshift 最佳实践](https://docs.aws.amazon.com/redshift/latest/dg/best-practices.html)
+ [使用 Amazon Redshift 数据 API 与Amazon Redshift 集群交互](https://aws.amazon.com/blogs/big-data/using-the-amazon-redshift-data-api-to-interact-with-amazon-redshift-clusters/)

## 操作说明
<a name="execute-redshift-sql-queries-using-terraform-epics"></a>

### 使用 Terraform 部署解决方案
<a name="deploy-the-solution-using-terraform"></a>


| Task | 说明 | 所需技能 | 
| --- | --- | --- | 
| **克隆存储库。** | 要克隆包含用于预调配 Amazon Redshift 集群的 Terraform 代码的 Git 存储库，请使用以下命令。<pre>git clone https://github.com/aws-samples/amazon-redshift-sql-deploy-terraform.git</pre> | DevOps 工程师 | 
| **更新 Terraform 变量。** | 要根据您的特定要求自定义 Amazon Redshift 集群部署，请更新 `terraform.tfvars` 文件中的以下参数。<pre>region                    = "<AWS_REGION>"<br />cluster_identifier        = "<REDSHIFT_CLUSTER_IDENTIFIER>"<br />node_type                 = "<REDSHIFT_NODE_TYPE>"<br />number_of_nodes           = "<REDSHIFT_NODE_COUNT>"<br />database_name             = "<REDSHIFT_DB_NAME>"<br />subnet_ids                = "<REDSHIFT_SUBNET_IDS>"<br />vpc_security_group_ids    = "<REDSHIFT_SECURITY_GROUP_IDS>"<br />run_nonrepeatable_queries = true<br />run_repeatable_queries    = true<br />sql_path_bootstrap        = "<BOOTSTRAP_SQLS_PATH>"<br />sql_path_nonrepeatable    = "<NON-REPEATABLE_SQLS_PATH>"<br />sql_path_repeatable       = "<REPEATABLE_SQLS_PATH>"<br />sql_path_finalize         = "<FINALIZE_SQLS_PATH>"<br />create_random_password    = false<br />master_username           = "<REDSHIFT_MASTER_USERNAME>"</pre> | DevOps 工程师 | 
| 使用 Terraform 部署资源。 | [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/patterns/execute-redshift-sql-queries-using-terraform.html) | DevOps 工程师 | 
| （可选）执行其他 SQL 查询。 | 示例存储库提供了几个 SQL 查询用于演示。要执行您自己的 SQL 查询，请将其添加到以下文件夹：`/bootstrap` `/nonrepeatable` `/repeatable` `/finalize` |  | 

### 监控 SQL 语句的执行
<a name="monitor-the-execution-of-sql-statements"></a>


| Task | 说明 | 所需技能 | 
| --- | --- | --- | 
| 监控 SQL 语句的部署。 | 您可以监控 Amazon Redshift 集群的 SQL 执行结果。有关显示 SQL 执行失败和成功执行的输出示例，请参阅[其他信息](#execute-redshift-sql-queries-using-terraform-additional)中的 *SQL 语句示例*。 | 数据库管理员、工程师 DevOps  | 
| 清理资源。 | 要删除 Terraform 部署的所有资源，请运行以下命令。<pre>terraform destroy</pre> | DevOps 工程师 | 

### 验证结果
<a name="validate-the-results"></a>


| Task | 说明 | 所需技能 | 
| --- | --- | --- | 
| 验证 Amazon Redshift 集群中的数据。 | [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/patterns/execute-redshift-sql-queries-using-terraform.html) | DBA、AWS DevOps | 

## 相关资源
<a name="execute-redshift-sql-queries-using-terraform-resources"></a>

**AWS 文档**
+ [亚马逊 Redshift 预配置的集群](https://docs.aws.amazon.com/redshift/latest/mgmt/working-with-clusters.html)
+ [亚马逊 Redshift 数据 API 问题疑难解答](https://docs.aws.amazon.com/redshift/latest/mgmt/data-api-troubleshooting.html)

**其他资源**
+ [命令：应用](https://developer.hashicorp.com/terraform/cli/commands/apply)（Terraform 文档）

## 附加信息
<a name="execute-redshift-sql-queries-using-terraform-additional"></a>

**解决方案演练**

要使用该解决方案，您必须以特定的方式组织您的 Amazon Redshift SQL 查询。所有 SQL 查询都必须存储在以 `.sql` 为扩展名的文件中。

在此模式提供的代码示例中，SQL 查询按以下文件夹结构进行组织。您可以修改代码（`sql-queries.tf` 和 `sql-queries.py`），使其适用于适合您独特使用案例的任何结构。

```
/bootstrap
     |- Any # of files
     |- Any # of sub-folders
/nonrepeatable
     |- Any # of files
     |- Any # of sub-folders
/repeatable
     /udf
          |- Any # of files
          |- Any # of sub-folders
     /table
          |- Any # of files
          |- Any # of sub-folders
     /view
          |- Any # of files
          |- Any # of sub-folders
     /stored-procedure
          |- Any # of files
          |- Any # of sub-folders
/finalize
     |- Any # of files
     |- Any # of sub-folders
```

鉴于上述文件夹结构，在 Amazon Redshift 集群部署期间，Terraform 按以下顺序执行查询：

1. `/bootstrap`

1. `/nonrepeatable`

1. `/repeatable`

1. `/finalize`

`/repeatable` 文件夹包含四个子文件夹：`/udf`、`/table`、`/view` 和 `/stored-procedure`。这些子文件夹指示 Terraform 执行 SQL 查询的顺序。

执行 SQL 查询的 Python 脚本是 `sql-queries.py`。首先，该脚本读取特定源目录的所有文件和子文件夹，例如 `sql_path_bootstrap` 参数。然后，该脚本通过调用 Amazon [ExecuteStatement](https://docs.aws.amazon.com/redshift-data/latest/APIReference/API_ExecuteStatement.html)Redshift API 操作来执行查询。一个文件中可能有一个或多个 SQL 查询。以下代码片段显示了 Python 函数，该函数针对 Amazon Redshift 集群执行存储在文件中的 SQL 语句。

```
def execute_sql_statement(filename, cluster_id, db_name, secret_arn, aws_region):
    """Execute SQL statements in a file"""
    redshift_client = boto3.client(
        'redshift-data', region_name=aws_region)
    contents = get_contents_from_file(filename),
    response = redshift_client.execute_statement(
        Sql=contents[0],
        ClusterIdentifier=cluster_id,
        Database=db_name,
        WithEvent=True,
        StatementName=filename,
        SecretArn=secret_arn
    )
    ...
```

Terraform 脚本 `sql-queries.tf` 创建用于调用 `sql-queries.py` 脚本的 [terraform\$1data](https://developer.hashicorp.com/terraform/language/resources/terraform-data) 资源。四个文件夹中的每一个都有一个 `terraform_data` 资源：`/bootstrap`、`/nonrepeatable`、`/repeatable`、和`/finalize`。以下代码片段显示了在 `/bootstrap` 文件夹中执行 SQL 查询的 `terraform_data` 资源。

```
locals {
  program               = "${path.module}/sql-queries.py"
  redshift_cluster_name = try(aws_redshift_cluster.this[0].id, null)
}

resource "terraform_data" "run_bootstrap_queries" {
  count      = var.create && var.run_nonrepeatable_queries && (var.sql_path_bootstrap != "") && (var.snapshot_identifier == null) ? 1 : 0
  depends_on = [aws_redshift_cluster.this[0]]

  provisioner "local-exec" {
    command = "python3 ${local.program} ${var.sql_path_bootstrap} ${local.redshift_cluster_name} ${var.database_name} ${var.redshift_secret_arn} ${local.aws_region}"
  }
}
```

您可以使用以下变量控制是否运行这些查询。如果您不想在 `sql_path_bootstrap`、`sql_path_nonrepeatable`、`sql_path_repeatable` 或 `sql_path_finalize` 中运行查询 ，请将其值设置为 `""`。

```
  run_nonrepeatable_queries = true
  run_repeatable_queries    = true
  sql_path_bootstrap        = "src/redshift/bootstrap"
  sql_path_nonrepeatable    = "src/redshift/nonrepeatable"
  sql_path_repeatable       = "src/redshift/repeatable"
  sql_path_finalize         = "src/redshift/finalize"
```

运行 `terraform apply` 时，无论脚本的结果如何，Terraform 都会考虑脚本完成后添加的 `terraform_data` 资源。如果某些 SQL 查询失败，而您想重新运行它们，则可以手动将该资源从 Terraform 状态中移除，然后重新运行 `terraform apply`。例如，以下命令将 `run_bootstrap_queries` 资源从 Terraform 状态中移除。

`terraform state rm module.redshift.terraform_data.run_bootstrap_queries[0]`

以下代码示例显示了 `run_repeatable_queries` 资源如何使用 [sha256 哈希值](https://developer.hashicorp.com/terraform/language/functions/sha256) 监视`repeatable`文件夹中的变化。如果更新了文件夹中的任何文件，Terraform 会将整个目录标记为更新。然后，Terraform 在下一 `terraform apply` 期间运行目录中的查询。

```
resource "terraform_data" "run_repeatable_queries" {
  count      = var.create_redshift && var.run_repeatable_queries && (var.sql_path_repeatable != "") ? 1 : 0
  depends_on = [terraform_data.run_nonrepeatable_queries]

  # Continuously monitor and apply changes in the repeatable folder
  triggers_replace = {
    dir_sha256 = sha256(join("", [for f in fileset("${var.sql_path_repeatable}", "**") : filesha256("${var.sql_path_repeatable}/${f}")]))
  }

  provisioner "local-exec" {
    command = "python3 ${local.sql_queries} ${var.sql_path_repeatable} ${local.redshift_cluster_name} ${var.database_name} ${var.redshift_secret_arn}"
  }
}
```

要完善代码，您可以实施一种机制，以检测并仅对 `repeatable` 文件夹中已更新的文件应用更改，而不是任意地将更改应用于所有文件。

**SQL 语句示例**

以下输出显示了 SQL 执行失败以及错误消息。

```
module.redshift.terraform_data.run_nonrepeatable_queries[0] (local-exec): Executing: ["/bin/sh" "-c" "python3 modules/redshift/sql-queries.py src/redshift/nonrepeatable testcluster-1 db1 arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:/redshift/master_user/password-8RapGH us-east-1"]
module.redshift.terraform_data.run_nonrepeatable_queries[0] (local-exec): -------------------------------------------------------------------
module.redshift.terraform_data.run_nonrepeatable_queries[0] (local-exec): src/redshift/nonrepeatable/table/admin/admin.application_family.sql
module.redshift.terraform_data.run_nonrepeatable_queries[0] (local-exec): -------------------------------------------------------------------
module.redshift.terraform_data.run_nonrepeatable_queries[0] (local-exec): Status: FAILED
module.redshift.terraform_data.run_nonrepeatable_queries[0] (local-exec): SQL execution failed.
module.redshift.terraform_data.run_nonrepeatable_queries[0] (local-exec): Error message: ERROR: syntax error at or near ")"
module.redshift.terraform_data.run_nonrepeatable_queries[0] (local-exec):   Position: 244
module.redshift.terraform_data.run_nonrepeatable_queries[0]: Creation complete after 3s [id=ee50ba6c-11ae-5b64-7e2f-86fd8caa8b76]
```

以下为成功执行后的输出示例。

```
module.redshift.terraform_data.run_bootstrap_queries[0]: Provisioning with 'local-exec'...
module.redshift.terraform_data.run_bootstrap_queries[0] (local-exec): Executing: ["/bin/sh" "-c" "python3 modules/redshift/sql-queries.py src/redshift/bootstrap testcluster-1 db1 arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:/redshift/master_user/password-8RapGH us-east-1"]
module.redshift.terraform_data.run_bootstrap_queries[0] (local-exec): -------------------------------------------------------------------
module.redshift.terraform_data.run_bootstrap_queries[0] (local-exec): src/redshift/bootstrap/db.sql
module.redshift.terraform_data.run_bootstrap_queries[0] (local-exec): -------------------------------------------------------------------
module.redshift.terraform_data.run_bootstrap_queries[0] (local-exec): Status: FINISHED
module.redshift.terraform_data.run_bootstrap_queries[0] (local-exec): SQL execution successful.
module.redshift.terraform_data.run_bootstrap_queries[0]: Creation complete after 2s [id=d565ef6d-be86-8afd-8e90-111e5ea4a1be]
```