尝试使用Terraform进行几十个RDS版本更新
首先
这是拉克斯2018年 Advent Calendar 第19天的文章。
这是一篇关于使用Terraform对几十个RDS(PostgreSQL)进行升级的文章。
总结来说
总结如下:
-
- terraforming で既存リソースをコード化した
-
- コードや構成を少し綺麗にした
- バージョン毎に git branch を切り替えて terraform apply を実行した
背景 –
我们选择使用 Terraform 进行此次版本升级的原因如下。
目的のメジャーバージョンにするには2回アップデートする必要があった
OK:9.a.x -> 9.b.y -> 9.c.z
NG:9.a.x -> 9.c.z
一台ずつコンソールポチポチは大変
awscliでのシェルスクリプトも考えたが、インフラリソースはコード管理しておきたい
terraform は触ったことがあった
任务进行的步骤
-
- terraform的初始设置
-
- 通过terrafoming生成现有资源的代码
-
- 导出到tfstate文件
-
- 整理和追加代码
-
- 准备更新工作的分支
- 执行terraform plan/apply
terraform的初始设置
首先,我们将进行terraform的初始设置。
本次我们将在工作终端上配置profile,并将S3存储桶作为后端使用。
※ 在开始之前,请预先创建s3存储桶和aws凭证。
※ 关于安装terraform和创建aws凭证等内容将被省略。
这是通过初始设置创建的.tf文件。
主要.tf
provider "aws" {
region = "ap-northeast-1"
profile = my_profile
}
terraform {
backend "s3" {
bucket = "terraform"
key = "tfstate.aws"
region = "ap-northeast-1"
profile = "my_profile_s3_only"
}
}
只需在包含main.tf的目录中运行$ terraform init,即可完成初始设置。
在永久修改地球的过程中的代码生成
由于RDS实例是现有资源,因此需要使用terraform来管理当前的配置。
尽管也可以通过执行terraform import命令来创建代码,但幸运的是,有一个名为terraform的工具可以从现有资源生成terraform代码,所以我选择了使用它。
关于安装,由于有docker容器镜像,所以选择以shell脚本的形式执行使用。
※虽然Terraform也可以通过容器镜像提供,但在这里忽略不提。
使用docker_terraforming.sh
#!/bin/sh
docker run \
--rm \
--name terraforming \
-e AWS_ACCESS_KEY_ID=xxx \
-e AWS_SECRET_ACCESS_KEY=xxx \
-e AWS_DEFAULT_REGION=ap-northeast-1 \
# 後々、tfstateファィルをマージしたいのでホストディレクトリをマウントしておく
-v /home/xxxx/git/terraform-dir/:/tmp \
quay.io/dtan4/terraforming:latest \
terraforming $@
当您指定参数并执行时,将生成与RDS数量相符的代码。
$ ./docker_terraforming.sh rds > rds.tf
$ cat rds.tf
resource "aws_db_instance" "rds_a_01" {
identifier = "rds_a_01"
allocated_storage = xx
storage_type = "xx"
engine = "postgres"
engine_version = "9.a"
instance_class = "db.t2.micro"
name = "xxxxxxxx"
username = "xxxxxxxx"
password = "xxxxxxxx"
port = xxxx
publicly_accessible = xxxxxxxx
availability_zone = "xxxxxxxx"
security_group_names = []
vpc_security_group_ids = ["sg-xxxxxxxx"]
db_subnet_group_name = "default"
parameter_group_name = "default.postgres9.a"
multi_az = true
backup_retention_period = x
backup_window = "xx:xx-xx:xx"
maintenance_window = "mon:yy:dd-mon:yy:dd"
final_snapshot_identifier = "rds_a_01-final"
(*snip*)
将tfstate文件导出到文件中
但是代码已经生成了,但是tfstate文件的状态是空的。
在 terraforming 中,有一个导出并合并到 tfstate 文件的功能,所以我们将使用它。
手续如下
从用terraform进行了初始设置的AWS账户获取tfstate文件。
$ terraform state pull > tfstate_original
2. 使用土壤改良技术来合并地球表面
$ ./docker_terraforming.sh s3 --tfstate --merge=/tmp/tfstate_original > tfstate_merge_after
将已经合并的tstate文件推送
$ terraform state push tfstate_merge_after
在推送tfstate文件之后执行terraform plan,发现显示了差异,但由于没有在代码中指定,它被应用为默认参数设置了。因此,我检查了RDS的配置并进行了必要的追加。
自动次要版本升级 = 假(默认为真)
最终确认了RDS的当前设置与计划中的差异没有很大差异,执行了terraform apply。
现在,现有的RDS资源已经成功导出完成。
代码整理,追加
由于 Terraforming 生成的代码被组合为一个单一文件,因此检查和修改变得十分困难。
为了按照 RDS 组进行组织,我进行了模块的配置。
.tf文件结构
.
├── main.tf
└── modules
|
|
└── rds
├── rds_a
│ ├── rds_a_01.tf
│ ├── rds_a_02.tf
│ ├── rds_a_03.tf
├── rds_b
│ ├── rds_b_01.tf
│ ├── rds_b_02.tf
│ ├── rds_b_03.tf
└── rds_c
├── rds_c_01.tf
├── rds_c_02.tf
└── rds_c_03.tf
(*snip*)
主.tf
terraform {
backend "s3" {
bucket = "terraform"
key = "tfstate.aws"
region = "ap-northeast-1"
profile = "my_profile_s3"
}
}
provider "aws" {
region = "ap-northeast-1"
profile = my_profile
}
# module を追記
module "rds_a" {
source = "./modules/rds/rds_a"
}
module "rds_b" {
source = "./modules/rds/rds_b"
}
module "rds_c" {
source = "./modules/rds/rds_c"
}
以下是为了更新而添加的tf文件。
rds_a.tf改写为中文的一个选项。
# terraforming が生成してくれた部分
resource "aws_db_instance" "rds_a_01" {
identifier = "rds_a_01"
allocated_storage = xx
storage_type = "xx"
engine = "postgres"
engine_version = "9.a"
instance_class = "db.t2.micro"
name = "xxxxxxxx"
username = "xxxxxxxx"
password = "xxxxxxxx"
port = xxxxxxxx
publicly_accessible = xxxxxxxx
availability_zone = "xxxxxxxx"
security_group_names = []
vpc_security_group_ids = ["sg-xxxxxxxx"]
db_subnet_group_name = "default"
parameter_group_name = "default.postgres9.a"
multi_az = xxxxxxxx
backup_retention_period = x
backup_window = "xx:xx-xx:xx"
maintenance_window = "mon:yy:dd-mon:yy:dd"
final_snapshot_identifier = "rds_a_01-final"
# terrafrom plan 確認後に追記した部分
monitoring_interval = 60
auto_minor_version_upgrade = false
skip_final_snapshot = false
# メジャーバージョンアップを行う為に追加
apply_immediately = true
allow_major_version_upgrade = true
# アップデートの処理で terraformのプロセスがタイムアウトしないように追加
timeouts {
create = "5h"
update = "5h"
delete = "5h"
}
}
修改代码后,执行terraform get以获取模块的配置。
$ terraform get
- module.rds_a
- module.rds_b
- module.rds_c
(*snip*)
声明执行状态
尽管构造变得更加简洁,但如果继续执行terraform plan,则可能会将其视为独立资源并产生差异。
在这种情况下,需要确保代码中的资源与 tfstate 文件的一致性。
作为解决方案,可以手动编辑 tfstate 文件,但在这里我们将执行 terraform state mv 来进行资源移动。
$ terraform state mv aws_db_instance.rds_a_01 module.aws_db_instance.rds_a_01
$ terraform state mv aws_db_instance.rds_a_02 module.aws_db_instance.rds_a_02
$ terraform state mv aws_db_instance.rds_a_03 module.aws_db_instance.rds_a_03
只需在通过数十个实例运行后使用 terraform plan 确认没有差异,就可以了。
准备用于更新工作的分支。
每次更新版本时创建一个分支,并更改以下参数。
# ブランチ毎に変更
engine_version = "9.b.y"
# メジャーバージョンにあったパラメータグループを作成して指定
parameter_group_name = "pg-9.b.y"
执行terraform plan/apply。
由于准备好了分支,我们将在每次版本更新时切换分支并执行 terraform apply。
-
- 9.a.x ブランチ が現在の状態である
-
- 9.b.y ブランチに切り替えて plan -> apply` で実行
- 9.c.z ブランチに切り替えて plan -> apply で実行
最终完成所有的RDS后,只需将9.c.z分支合并到master分支即可完成。
实际的升级工作
指定适用范围
因为我的心脏并不足够强大,无法一次性更新所有RDS生产机器,所以我使用了目标选项来指定要应用的资源范围。
$ terraform apply -target=aws_db_instance.rds_a_01 -target=aws_db_instance.rds_a_02
将版本升级工作进行任务化。
由于RDS在版本升级时会被关闭,因此需要设定维护日期和时间来停止服务。
RDS 升级前后的处理(停止应用程序和确认更新后的操作)也会发生。我们已经在 rundeck 中创建了作业,并在深夜自动执行,作为解决方案进行版本升级工作。
在进行任务化时,需要在执行terraform apply之前确定是否应用,因此使用了-auto-approve选项来跳过。
$ terraform apply -auto-approve
以下是Rundeck作业的执行顺序。
※ 关于Rundeck的设置内容将被省略。

这样一来,我们可以利用 terraform + rundeck 成功完成了数十台 RDS 的版本升级工作。
回顾
在做事之前没有详细制定设计或工作方针。
- terraform 利用の設計や方針を事前に決めてたら state mv での作業は不要だったなと思いました
作业的预期太过乐观了
-
- ジョブ化は当初想定しておらず検証時の時間とRDSアップデートの並列実行(五台同時)にスケジューリングして手動で行っていく予定だったが予想以上に時間と作業規模が大きかった。
- 解決策としての rundeck については自分ではなく別メンバの方がジョブを作作成しました、この場を借りてお礼申し上げます
我未来想做的事情
我們目前只將RDS編碼和管理,但我們希望將其他資源如VPC和安全群組也進行編碼和管理。
虽然如此,考虑到目前的情况,我们需要手动进行设置更改,以确保AWS资源、tfstate和代码的状态保持一致。因此,我们正在考虑以下配置。

在Rundeck上执行Terraform plan,如果有差异则通知到Slack。
如果有差异存在,手动应用。
由于代码是硬编码的,所以我希望能够进行重构。我也想试试使用模块、变量和工作空间。
Please refer to the following. (请参考以下内容。)
rundeck – 运维日程
terraforming – 地球转型
teraform – 地球转型
使用 Terraform 的 state mv 将手动编写的资源移至模块