0%

什么是基础设施即代码

基础设施即代码(Infrastructure as Code)是一种将基础设施的配置和管理过程自动化的方法。它借鉴了软件开发中的一些实践,如版本控制、自动化测试和持续集成,将基础设施的配置和管理过程描述为可执行的代码。

通过使用基础设施即代码,开发团队可以将基础设施的配置和管理过程存储为代码,并将其纳入版本控制系统中。这样一来,团队成员可以对基础设施进行版本控制、进行代码审查和合并,并且可以使用自动化工具来验证和部署基础设施的更新。

借助基础设施即代码,可以实现以下优势:

  • 可重复性和可靠性:通过代码来定义基础设施,可以确保环境的一致性,避免了人工配置的差错,并且可以重复使用、再现和共享配置。
  • 自动化和可伸缩性:通过自动化工具,可以更快速、可靠地创建、更新和销毁基础设施,提高部署效率和灵活性,并支持快速的扩展和缩减。
  • 文档化和可视化:基础设施即代码的代码本身可以作为文档,提供对基础设施配置的清晰描述,同时可以生成可视化的拓扑图或文档来展示基础设施的结构和关系。
    总之,基础设施即代码是一种有效的方法,可以提高基础设施的可管理性、可测试性和可靠性,促进基础设施和应用程序的协同演进。

以上是ChatGPT对基础设施即代码的理解。

我们在基于 Terraform 落地基础设施即代码时,遇到了一个很有趣的问题,这个问题让我对基础设施即代码有了更深刻的理解。

问题

使用 Terraform 托管腾讯云 MySQL 实例时,这这么一个字段:

1
first_slave_zone - (Optional, String, ForceNew) Zone information about first slave instance.

这个字段指的是第一个备库可用区,注意这个字段是 ForceNew 定义,也就是说,如果这个字段发生变化,那么这个实例就会被重新创建。
我们的要求是主备在不同的可用区,所以我们在配置文件中这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
resource "tencentcloud_mysql_instance" "main" {
...
availability_zone = zone1
first_slave_zone = zone2
...

tags = {
}

parameters = {
character_set_server = "utf8mb4"
}

}

看起来一切都很完美,但是当有一天dba在对一个实例进行常规的磁盘扩容时,发现这个实例被重新创建了,老的实例被销毁了,直接故障十分钟(发现销毁后从腾讯云回收站找回)。

首先要说明的是我们的 Terraform 不是用过 cli 的方式使用的,我们在内部落地了一套自动化方案,具体可以翻前面的文章。对于任何变化,都是auto approve。

分析

通过查看日志,我们发现了这么一段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
~ update in-place
-/+ destroy and then create replacement

Terraform will perform the following actions:

# tencentcloud_mysql_instance.main must be replaced
-/+ resource "tencentcloud_mysql_instance" "main" {
~ first_slave_zone = "ap-beijing-7" -> "ap-beijing-5" # forces replacement
~ gtid = 1 -> (known after apply)
~ id = "xxxxx" -> (known after apply)
+ internet_host = (known after apply)
~ internet_port = 0 -> (known after apply)
~ intranet_ip = "xxxxxxxxx" -> (known after apply)
~ locked = 0 -> (known after apply)
- pay_type = -1 -> null
- period = -1 -> null
~ status = 1 -> (known after apply)
- tags = {} -> null
~ task_status = 0 -> (known after apply)
~ volume_size = 800 -> 1200
# (22 unchanged attributes hidden)
}

# tencentcloud_mysql_readonly_instance.readonly will be updated in-place
~ resource "tencentcloud_mysql_readonly_instance" "readonly" {
id = "xxxx"
~ master_instance_id = "xxxxx" -> (known after apply)
tags = {}
~ volume_size = 800 -> 1200
# (22 unchanged attributes hidden)
}

Plan: 1 to add, 1 to change, 1 to destroy.

Changes to Outputs:
~ instance_id = "xxxxx" -> (known after apply)
~ instance_ip = "xxxxx" -> (known after apply)
~ locked = 0 -> (known after apply)
~ status = 1 -> (known after apply)
~ task_status = 0 -> (known after apply)
tencentcloud_mysql_instance.main: Destroying... [id=xxxxxx]
tencentcloud_mysql_instance.main: Still destroying... [id=xxxx, 10s elapsed]
tencentcloud_mysql_instance.main: Destruction complete after 12s
tencentcloud_mysql_instance.main: Creating...
tencentcloud_mysql_instance.main: Still creating... [10s elapsed]

很容易发现 first_slave_zone = “ap-beijing-7” -> “ap-beijing-5” # forces replacement 这个字段导致了销毁重建。
但是问题是我们的备库实例怎么从 ap-beijing-5 变成了 ap-beijing-7哪?

了解MySQL主备架构的都知道,主备切换是一个非常常见的现象,腾讯的技术支持表示,网络抖动、运维动作都可能会出现主备切换,而主备切换后,备库的可用区会变成主库的可用区,这个时候,如果我们的配置文件中没有更新 first_slave_zone 这个字段,就会导致Terraform中判定这个字段改动了,最可怕的是这个字段还是 ForceNew。

腾讯的技术同学一开始表示,他们的系统没问题,是我们使用的问题,不过我们也承认,我们对接的过程中对MySQL的这个架构理解不够深,假如我们足够理解的话,我们不会认同这种Api的设计。

所以在这里重新回到文章的标题,仔细思考什么是基础设施即代码。按照我们的理解我们固定在代码中的配置理论上不应该发生改变,假如说改变了,一定是不符合预期的,需要回到代码中定义的状态。
但是我们这个case中,切换应该是预期合理的行为,不应该销毁实例重建,因此我们认为这是Api设计问题。

像这种主备状态,我们认为应该是一个状态,而不是一个配置,不应该固定在代码中。这种思想类比到k8s的Api中也是一样,资源都有Spec和Status两个字段,Spec是用户指定的,Status是系统自动计算的,用户不应该去指定更不能修改。

为了证明我们的理解没问题,说服他们,我们也看了其他云厂商的Api设计,比如Aws的:

1
availability_zones - (Optional) List of EC2 Availability Zones for the DB cluster storage where DB cluster instances can be created. RDS automatically assigns 3 AZs if less than 3 AZs are configured, which will show as a difference requiring resource recreation next Terraform apply.

这里的设计是Optional,而且是一个列表,而不是一个字符串,这样设计用户指定的是一个可用区列表,而不是一个可用区,这样的设计就不会出现我们这个case中的问题。
无论实例在运行中如何切换都不影响,只要主备是在一个可用区列表中,就是符合预期的。

最终

在腾讯云最新的 Api 中仅仅是把 first_slave_zone 从 ForceNew 改成了 Optional,但是这样的设计还是会出现跟用户操作无关的 first_slave_zone 改变去重建备库。作为用户的话,我很冤枉,我没动这个字段,但是我的备库重建了,我反正无法接受这个事情。

在这个case中,我们认为腾讯云的对于MySQL的Api设计是有问题的,我们也提出了我们的理解,希望腾讯云能够改进这个Api设计,让用户的体验更好。
另外对于基础设施即代码的实践过程中,无论是设计Api还是对接Api的时候,需要充分甄别什么是配置,什么是状态。

赞赏一杯咖啡