0%

IaC基础设施即代码思想下的Terraform工具实战

1 IaC基础设施即代码思想下的Terraform工具实战

本文针对IaC(Infrastructure as Code)即基础设施即代码概念进行了简单介绍

同时通过Terraform工具去实战讲解如何远程操作我个人位于阿里云公有云以及VMware vSphere8私有云的的基础设施

在不久的将来,随着信创产业的不断壮大,信创云也会大量落地,由于信创的应用场景和客户的特殊性会要求集约化建设,而通过基础设施即代码IaC的理念去管理信创云基础设施全生命周期不失为一个好的数字化方案

2 前言

2.1 Iac是Devops的实现

Devops是数字化转型的重要环节,企业在数字化转型的过程中绕不开DevOps,它的核心在于IT如何赋能业务,创造价值,关键词在敏捷价值

大部分企业数字化框架都把DevOps作为最核心的能力之一,Devops的目标是支持业务敏捷,其中一个核心能力就是将软件交付过程和运维管理自动化,包括自动化部署、自动化测试、自动化运维等,其中就需要落实到IT基础设施

基础设施即服务(Infrastructure as a Service,IaaS)、平台即服务(Platform as a Service,PaaS)、软件即服务(Software as a Service,SaaS)是传统对云计算的三层分类,伴随着云原生的不断发展,IT基础设施架构也在不断演变

Devops的自动化落实到基础设施方面,应该更多实现以软件自动化理念管理,通过编写和执行代码去管理IT基础设置的整个生命周期(定义、创建、更新、销毁),减少手动方式,即采用IaC(Infrastructure as Code)即基础设施即代码

而基础设施即代码IaC中的C就是Code的意思,它是一种观念的转变,从以代码的方式去管理,其将运维的各个工作都视为与软件相关,甚至一些明显针对硬件的工作,即将所有事物都在代码中进行管理,包括了服务器、数据库、网络、容器、虚拟机甚至多云管理等等

目前我们针对上述基础设施的管理,绝大部分还留停在网页上的鼠标点击或者是手动跑一个shell脚本等内容范畴

部分企业运维自动化方法实践中,有诸如PXE或者二次封装的PXE进行上百台或者更高规模的服务器操作系统部署,也有通过ansible直接自动化部署K8S集群或者Ceph分布式存储,或者通过某些特定云平台API接口去实现云资源的管理

2.2 Terraform是Iac的实现

IaC是一种思想,从广义上说实现这种思想的工具包括了前述诸如ansible配置管理类工具,效率远远优于shell脚本,可用作大规模、分布式、统一管理的场景

而目前IaC在国外目前最火的一定是HashiCorp公司的Terraform工具,它利用一种人类可读的语言【类似K8S中Yaml的声明式的代码】去定义管理基础设施的整个生命周期

Terraform最大的特点就是通过Provider插件为基础设施的管理者提供一个统一的接口,不用像CloudFormation那样绑定AWS平台,而Terraform可以以IaC的思想可以从容地应付以下的场景

1、通过阿里巴巴的Provider插件来管理阿里云资源

2、通过AWS的Provider插件来管理AWS云资源

3、通过vMware的Provider插件来管理多个vSphere虚拟化平台上集群建立和管理,包括虚拟机、存储池和网络资源

4、通过kubernetes的Provider插件可以完全管理K8S的资源

Terraform可以同时编排各种云平台或是其他基础设施的资源,涵盖了公有云、私有云、数据库、网络、日志、监控等等

2.3 Terraform的优势

1、理论上他可以管理任何基础设施

2、上层管理仅仅考虑如何定义、如何声明,不必考虑太多底层实现

3、多云架构的部署

在Terraform的Provider插件仓库,有着进3000多个官方、第三方认证以及社区的Provider插件

image-20231010213925819

不管是数字化转型还是数智化转型,企业自动化方法和工具绝对是每个企业需要做好的功课,绝对能够帮助你业务敏捷和业务价值赋能上取得突破

在将来,大型超算中心中的超算网络的部署和监控,计算节点和GPU节点的上线和下线管理、分布式存储的集群各节点创建、销毁都变得越来越复杂

掌握了IaC的思想后,我们可以利用自研的类似Terraform工具的信创版本,变成“撒豆成兵”的魔法棒,快速地去管理整个上百台或更大规模的裸金属上线,操作系统的安装,各类别集群的创建、运维和销毁,对数据库等各中间件的部署、配置都能做到统一的定义和管理,同时对超算网络、超算算力资源的监控、运维管理都能纳入一个统一的管理平台上,帮助我们自己更加去实现业务敏捷和业务价值赋能

3 架构

3.1 整体架构

Terraform的架构可以简单总结下来,包括了二进制可执行文件,Provider插件,HashiCorp自研HCL语法用于资源定义

image.png

Terraform的可执行文件是通过RPC直接调用Provider插件,而Provider插件通过调用各资源提供的API接口来实现操作远程资源

3.2 二进制可执行文件

查看AMD64 Linux架构

1
2
[root@RHEL9 ~]# file /usr/bin/terraform 
/usr/bin/terraform: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=sasOoU4AIfLAiPc7Jc7V/jzBvRMFg2EqfqllVPnTA/xJWQL3a3yzDsf0rIp4PY/455Up-yqrWuKiow62bxg, stripped

ELF(Executable Linkable Format)格式是Linux中的可执行文件格式,x86-64为平台

可以看出软件使用golang语言进行开发编译,登录https://github.com/hashicorp/terraform显示terraform项目的开发语言

image-20231010121035617

值得一提的是这个hashicorp公司,是一家专注于DevOps工具链的公司,旗下有多明星级产品,多个产品的start数都超过20K

image-20231017094417089

Consul不用多说,是一套开源的分布式服务发现和配置管理系统,微服务注册中心

旗下的Vault是一款企业级私密信息管理工具,例如数据库凭证和API密钥、Vault可以解决在云环境下Vault控制敏感信息全生命周期的管理(创建、生成、使用和销毁)

Vagrant简化虚拟化的软件配置管理,来提高开发效率,例如使用vagrant init hashicorp/bionic64直接操作诸如VirtulBox等虚拟机软件,传统VirtulBox建立虚拟机则必须使用鼠标点点点

除了vagrant使用Ruby开发,其它均使用Go语言开发

3.3 Provider插件

众所周知,为了增强程序的功能的可扩展性,往往采取插件的方式进行架构设计,例如通过将插件安装在程序的类似plugin目录下,然后以重启或者热加载的方式使用程序新的功能

Terraform也是通过Provider作为插件提供扩展功能,不过Provider插件跟一般形式不一样,他特殊点之一在于各个Provider插件都是独立的进程,与Terraform进程之间通过RPC进行通讯

为此Terraform在研发当初就决定使用的是HashiCorp自研的go-plugin

Provider插件的编写者根据Terraform所制定的插件框架来定义各种data和resource【3.4 HCL语法介绍的数据源和资源】,并实现相应的CRUD方法

Terraform引擎首先读取并分析用户编写的Terraform代码,形成一个由data与resource组成的图(Graph),再通过RPC调用这些data与resource所对应的Provider插件

Provider插件调用目标平台提供的SDK,或是直接通过调用Http(s) API来操作目标平台

事实上,正因为如此,Terraform也不是银弹,如果云提供商或者基础设施的提供商并没有提供某一特定功能的接口API,那么插件也不能完成此项功能,最终Terraform也只能望江长叹

3.3.1 Provider插件分类

Provider分类包括三种,分别是官方插件(Offical)、第三方认证插件(Partner)和社区版本插件(Community)

其中官方插件左上角有图标,并且显示by hashicorp,官方插件的兼容性、稳定性都是经过测试,建议选择官方插件

image-20231009173632474

3.3.2 go-plugin插件

我们知道Go语言缺乏动态加载代码的机制,Go程序通常是独立的二进制文件,因此难以实现类似于C++的插件系统

即使go的最新标准引入了go plugin机制,但是由于限制性条件比较多导致在生产环境中不是很好用,比如插件的编写环境和插件的使用环境要保持一致,如gopath、go sdk版本等

HashiCorp公司开源的go-plugin库解决了上述问题,允许应用程序通过本地网络(本机)的gRPC调用插件,规避了Go无法动态加载代码的缺点

go-plugin是一个通过RPC实现的Go插件系统,并在Packer、Terraform, Nomad、Vault等由HashiCorp主导的项目中均有应用。

通过file命令查看文件属性,可以发现这就是使用golang编译的二进制文件

1
2
[root@RHEL9 terraform]# file .terraform/providers/registry.terraform.io/hashicorp/alicloud/1.211.0/linux_amd64/terraform-provider-alicloud_v1.211.0 
.terraform/providers/registry.terraform.io/hashicorp/alicloud/1.211.0/linux_amd64/terraform-provider-alicloud_v1.211.0: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=injh3V21xczeBeZIkSGx/iLdSBmXvPDlxS9uYhnvQ/hDPFRdKPpZm8VNbvwM7R/IBXnWqPR-v8d-avoBHFI, stripped

3.4 自研HCL语法

首先Terraform配置文件必须使用UTF-8编码格式,而且Terraform支持JSON语法,但是建议仍采用其自家的HCL语法编制

生成环境下一般采用模块式的架构,一个文件夹就是一个模块,生成环境推荐采用资源和变量分离的方式

var.tf包含了所有的输入变量,而main.tf包这个模块所需要创建和维护的资源,如果有输出变量,建议将所有输出变量单独放在一个文件中

可以把创建的资源分布到不同的代码文件中,例如

1、阿里云的网络和交换机资源可以放在一个文件夹内,作为一个模块

2、安全组作为一个资源形成tf文件,放在一个文件夹下单独形成一个模块

3、实例作为一个资源形成tf文件,放在一个文件夹下单独形成一个模块

4、根文件夹包括main.tf,根文件夹包括了上述各子文件夹,同时通过嵌入引用上述各种资源

HCL的语法其实非常适合人类读写,因此本文不对语法做太多讲解,注重将后续看懂实战的几个关键知识点简单剖析下

3.4.1 资源

在Terraform,最重要就是资源(resource),资源通过resource块定义

1
2
3
4
5
resource "alicloud_vswitch" "vsw" {
vpc_id = alicloud_vpc.vpc.id
cidr_block = "172.16.128.0/20"
zone_id = "cn-shanghai-b"
}

resource是关键字,其中alicloud_vswitch 就是资源类型,后面vsw是name

类型和name组合在一起要确保同一个模块中是唯一的,后续其它资源可以资源参数通过alicloud_vswitch.vsw.id进行引用

例如资源类型为alicloud_instance,name是instance,其中一个资源参数vswitch_id就实现了上述关系的引用

1
2
3
resource "alicloud_instance" "instance" {
vswitch_id = alicloud_vswitch.vsw.id
}

3.4.2 数据源

在Terraform,还有一种只读不写的类型就是数据源,数据源通过data块定义

1
2
3
4
5
6
7
8
9
10
data "vsphere_distributed_virtual_switch" "vds" {
name = "GA"
datacenter_id = data.vsphere_datacenter.datacenter.id
}

data "vsphere_network" "network" {
name = "NET1"
distributed_virtual_switch_uuid = data.vsphere_distributed_virtual_switch.vds.id
datacenter_id = data.vsphere_datacenter.datacenter.id
}

在上述代码块中,有2个块定义,其中第一个定义了一个类型是vsphere_distributed_virtual_switch,name是vds的数据源

第二个块定义了一个是类型是vsphere_network,name是network的数据源,其中distributed_virtual_switch_uuid参数引用了data.vsphere_distributed_virtual_switch.vds.id

image-20231017105508092

实际知晓VMware vSphere部署的人基本看名称就知道,这两个数据源其实就是在查询名称是GA的分布式交换机,和名称是NET1的分布式交换下端口组

特别注意

我们在之前介绍的资源(使用resource块定义的)会触发Terraform对基础设施对象进行增删改操作

而数据源data只会触发读取操作

同样的,对于data数据源,类型和name组合在一起要确保同一个模块中是唯一的

我在实战一中都是用了resource块定义,而在实战二中使用了data和resource块分别定义,注意比较

3.5 状态管理

Terraform会记住当前基础设施的状态,并将之与代码所描述的期望状态进行比对,这是有别于ansible等配置管理工具不同的地方

通过本地状态文件,Terraform会先比较再执行,秘诀就在于本地状态文件terraform.tfstate以及备份文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@RHEL9 terraform-aliyun]# ll
total 24
-rw-r--r--. 1 root root 653 Oct 12 15:12 1
-rw-r--r--. 1 root root 2216 Oct 15 20:35 aliyun.tf
-rw-r--r--. 1 root root 181 Oct 17 12:42 terraform.tfstate
-rw-r--r--. 1 root root 10602 Oct 17 12:42 terraform.tfstate.backup
[root@RHEL9 terraform-aliyun]# cat terraform.tfstate
{
"version": 4,
"terraform_version": "1.6.1",
"serial": 73,
"lineage": "76aae6bf-329b-897c-54ca-8ac634777ef0",
"outputs": {},
"resources": [],
"check_results": null
}

有利就有弊,注意这种本地状态文件会将密钥等机密信息已明文方式保存起来,在实际生成环境中采用terraform Backend解决办法

老生常谈的话题就是,千万不要将密钥等机密信息以硬编码方式写入代码中

4 部署

Terraform的部署操作其实非常简单,主要包括安装、初始化、定义、使用四个步骤

1、二进制可执行文件

2、官方查找provider插件,定义tf文件,使用terraform init初始化并下载provider插件

3、定义tf文件,使用HCL语法定义需要管理的资源

4、使用terraform applyterraform destroy等命令对资源进行生命周期的管理

4.1 安装

4.1.1 软件包方式安装

我们采用CentOS/RHEI架构环境进安装

1
2
3
yum install -y yum-utils
yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
yum -y install terraform

实际上以terraform软件包的方式安装也是二进制安装方式,最终在/usr/bin/目录下生成terraform二进制文件

1
2
[root@RHEL9 ~]# rpm -ql terraform
/usr/bin/terraform

验证terraform版本为v1.6.0

1
2
3
[root@RHEL9 ~]# terraform --version
Terraform v1.6.0
on linux_amd64

4.1.2 直接二进制方式

也可以直接下载二进制文件

例如目标机器是AMD64,Version: 1.6.0架构

1
wget https://releases.hashicorp.com/terraform/1.6.0/terraform_1.6.0_linux_amd64.zip

下载后剩下就是复制到目标目录,授权可执行权限

4.2 使用Provider插件

使用terraform init进行初始化

会根据资源参数信息,开始下载provider插件

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
[root@RHEL9 ~]# terraform init

Initializing the backend...

Initializing provider plugins...
- Finding latest version of hashicorp/alicloud...
- Installing hashicorp/alicloud v1.211.0...
- Installed hashicorp/alicloud v1.211.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.


│ Warning: Additional provider information from registry

│ The remote registry returned warnings for registry.terraform.io/hashicorp/alicloud:
│ - For users on Terraform 0.13 or greater, this provider has moved to aliyun/alicloud. Please update your source in required_providers.


Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

下载完插件后,我们查看当前目录结构,发现在当前目录生成.terraform一个隐藏目录,可执行文件会被下载到指定目录

1
2
3
4
5
6
7
8
9
10
11
12
[root@RHEL9 terraform]# tree -a
.
├── aliyun.tf
├── .terraform
│ └── providers
│ └── registry.terraform.io
│ └── hashicorp
│ └── alicloud
│ └── 1.211.0
│ └── linux_amd64
│ └── terraform-provider-alicloud_v1.211.0
└── .terraform.lock.hcl

通过file命令查看文件属性,可以发现这就是使用golang编译的二进制文件

1
2
[root@RHEL9 terraform]# file .terraform/providers/registry.terraform.io/hashicorp/alicloud/1.211.0/linux_amd64/terraform-provider-alicloud_v1.211.0 
.terraform/providers/registry.terraform.io/hashicorp/alicloud/1.211.0/linux_amd64/terraform-provider-alicloud_v1.211.0: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=injh3V21xczeBeZIkSGx/iLdSBmXvPDlxS9uYhnvQ/hDPFRdKPpZm8VNbvwM7R/IBXnWqPR-v8d-avoBHFI, stripped

4 实战一 公有云阿里云

接下来将对Terraform做安装部署、原理分析以及实际案例的讲解

本文使用两个实战案例去介绍Terraform工具,包括一个是阿里云公有云的资源管理,另一个是Vmware vSphere8私有云的资源管理

注意实战一全部使用了resource关键字,而实战二使用了data和resources,注意区别

如前述,Terraform的部署操作主要包括安装、初始化、定义资源、使用四个步骤

安装的步骤详见4.1 安装章节内容,这里直接从初始化开始

4.1 初始化

创建terraform的工作目录

1
2
[root@RHEL9 ~]# mkdir -p terraform
[root@RHEL9 ~]# cd terraform/

开始编写配置文件,这里以远程管理阿里云资源为例

1
2
3
4
5
provider "alicloud" {
access_key = "${var.access_key}"
secret_key = "${var.secret_key}"
region = "${var.region}"
}

千万不要将密钥等机密信息以硬编码方式写入代码中

provider这一行表明提供的阿里云的接口

access_key和secret_key分别是阿里云的access_key、secret_key

在阿里云的账户中,通过右上角的AccessKey 管理可以获得

image-20231011203205808

region 是管理的区域,这里选择cn-shanghai-b

注意region的区域,可以多注意下,一般都是cn-beijing-a或者cn-shanghai-b类似这些

使用terraform init进行初始化,开始下载阿里云provider插件

4.2 定义资源

我们目标是在通过代码定义自动化在阿里云上创建一个虚拟机,付费类型为按量付费,地域为华东2

image-20231017102517077

实例规格选择为ecs5-lc1m1.small

image-20231017102638568

镜像我们选择龙蜥OS,版本为8.8 64位

image-20231017102924706

硬盘我们选择高效云盘

image-20231017103125844

根据上面的需求我们使用HCL语言定义tf文件

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
provider "alicloud" {
access_key = "xxxxx"
secret_key = "xxxxx"
region = "cn-shanghai"
}


//vpc network
resource "alicloud_vpc" "vpc" {
vpc_name = "tf_test"
cidr_block = "172.16.0.0/16"
}

//vpc switch
resource "alicloud_vswitch" "vsw" {
vpc_id = alicloud_vpc.vpc.id
cidr_block = "172.16.128.0/20"
zone_id = "cn-shanghai-b"
}

//security_group
resource "alicloud_security_group" "default" {
name = "default"
vpc_id = alicloud_vpc.vpc.id
security_group_type = "normal"
description = "tf security group"
}

resource "alicloud_security_group_rule" "allow_80_tcp" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "intranet"
policy = "accept"
port_range = "80/80"
priority = 1
security_group_id = alicloud_security_group.default.id
cidr_ip = "0.0.0.0/0"
}

resource "alicloud_security_group_rule" "allow_443_tcp" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "intranet"
policy = "accept"
port_range = "443/443"
priority = 1
security_group_id = alicloud_security_group.default.id
cidr_ip = "0.0.0.0/0"
}
resource "alicloud_security_group_rule" "allow_22_tcp" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "intranet"
policy = "accept"
port_range = "22/22"
priority = 1
security_group_id = alicloud_security_group.default.id
cidr_ip = "0.0.0.0/0"
}

resource "alicloud_instance" "instance" {
availability_zone = "cn-shanghai-b"
security_groups = alicloud_security_group.default.*.id

instance_type = "ecs.t5-lc1m1.small"
system_disk_category = "cloud_efficiency"
system_disk_name = "tf_system_disk_name"
system_disk_description = "tf_system_disk_description"
image_id = "anolisos_8_8_x64_20G_rhck_uefi_alibase_20230308.vhd"
instance_name = "tf_test_instance"
vswitch_id = alicloud_vswitch.vsw.id
internet_max_bandwidth_out = 1
internet_charge_type = "PayByTraffic"
password = "root123"
}

4.2.1 资源参数详解

1、provider "alicloud" :使用阿里云provdier插件,access_keysecret_keyregion的就是代表访问权限和可用域

2、resource "alicloud_vpc":定义了阿里云的专用网络,这里是标准写法,不要修改,其中cidr_block

1、建议使用RFC私网地址作为专有网络的网段如10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,多VPC互通场景或混合云场景需确保地址规划不能冲突

2、不能使用 100.64.0.0/10、224.0.0.0/4、127.0.0.0/8 或 169.254.0.0/16 网段作为VPC的网段

3、resource "alicloud_vswitch":定义了阿里云的交换机,这里也是标准写法,不要修改,vpc_id要和专用网络的id进行匹配cidr_block要和专用网络匹配

4、resource "alicloud_security_group": 定义了阿里云的安全组,这里也是标准写法,不要修改,vpc_id要和专用网络的id进行匹配

5、接下来定义了两个安全组策略resource "alicloud_security_group_rule" "allow_80_tcp"resource "alicloud_security_group_rule" "allow_443_tcp"resource "alicloud_security_group_rule" "allow_22_tcp":注意这里的type、ip_protocol、port_range等各种资源参数。实际上和网页上进行比较下就能理解

image-20231017110540794

6、resource "alicloud_instance" "instance":定义了阿里云ESC实例,availability_zone 代表实例的地域,instance_type实例类型,这里选择ecs.t5-lc1m1.small,image_id为”anolisos_8_8_x64_20G_rhck_uefi_alibase_20230308.vhd”

可以通过访问https://ecs.console.aliyun.com/#/image/region/cn-shanghai/systemImageList查看镜像的具体名称

internet_charge_type为实例类型,这里为按流量付费,password为创建后实例的root密码

4.3 使用

定义资源完毕后,我们可以通过terraform plan去计算比较后续动作与当前状态

注意

Terraform在这点上是有别于ansible等配置管理工具的,ansible是要满足幂等,都是在远端先执行,通过代码逻辑去比较处理后续动作

而Terraform是通过当前代码内资源的定义和当前远端资源最新状态?

Terraform是如何做到这点的

Terraform会将最新的资源状态保存在当前目录的terraform.tfstate文件内

使用terraform apply命令,terraform会计算需要创建的资源

1
2
[root@RHEL9 terraform]# terraform apply
alicloud_vpc.vpc: Refreshing state... [id=vpc-uf6ebhni889ea230gw21b]

通过资源比较,提示有7个资源会被创建

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create

Terraform will perform the following actions:

# alicloud_instance.instance will be created
+ resource "alicloud_instance" "instance" {
+ availability_zone = "cn-shanghai-b"
+ cpu = (known after apply)
+ credit_specification = (known after apply)
+ deletion_protection = false
+ deployment_set_group_no = (known after apply)
+ dry_run = false
+ host_name = (known after apply)
+ http_endpoint = (known after apply)
+ http_put_response_hop_limit = (known after apply)
+ http_tokens = (known after apply)
+ id = (known after apply)
+ image_id = "anolisos_8_8_x64_20G_rhck_uefi_alibase_20230308.vhd"
+ instance_charge_type = "PostPaid"
+ instance_name = "tf_test_instance"
+ instance_type = "ecs.n1.tiny"
+ internet_charge_type = "PayByTraffic"
+ internet_max_bandwidth_in = (known after apply)
+ internet_max_bandwidth_out = 1
+ ipv6_address_count = (known after apply)
+ ipv6_addresses = (known after apply)
+ key_name = (known after apply)
+ maintenance_action = (known after apply)
+ memory = (known after apply)
+ network_interface_id = (known after apply)
+ os_name = (known after apply)
+ os_type = (known after apply)
+ password = (sensitive value)
+ primary_ip_address = (known after apply)
+ private_ip = (known after apply)
+ public_ip = (known after apply)
+ role_name = (known after apply)
+ secondary_private_ip_address_count = (known after apply)
+ secondary_private_ips = (known after apply)
+ security_groups = (known after apply)
+ spot_duration = (known after apply)
+ spot_strategy = (known after apply)
+ status = (known after apply)
+ stopped_mode = (known after apply)
+ subnet_id = (known after apply)
+ system_disk_category = "cloud_efficiency"
+ system_disk_description = "tf_system_disk_description"
+ system_disk_id = (known after apply)
+ system_disk_name = "tf_system_disk_name"
+ system_disk_performance_level = (known after apply)
+ system_disk_size = 40
+ volume_tags = (known after apply)
+ vswitch_id = (known after apply)
}

# alicloud_security_group.default will be created
+ resource "alicloud_security_group" "default" {
+ description = "tf security group"
+ id = (known after apply)
+ inner_access = (known after apply)
+ inner_access_policy = (known after apply)
+ name = "default"
+ security_group_type = "normal"
+ vpc_id = (known after apply)
}

# alicloud_security_group_rule.allow_22_tcp will be created
+ resource "alicloud_security_group_rule" "allow_22_tcp" {
+ cidr_ip = "0.0.0.0/0"
+ id = (known after apply)
+ ip_protocol = "tcp"
+ nic_type = "intranet"
+ policy = "accept"
+ port_range = "22/22"
+ prefix_list_id = (known after apply)
+ priority = 1
+ security_group_id = (known after apply)
+ type = "ingress"
}

# alicloud_security_group_rule.allow_443_tcp will be created
+ resource "alicloud_security_group_rule" "allow_443_tcp" {
+ cidr_ip = "0.0.0.0/0"
+ id = (known after apply)
+ ip_protocol = "tcp"
+ nic_type = "intranet"
+ policy = "accept"
+ port_range = "443/443"
+ prefix_list_id = (known after apply)
+ priority = 1
+ security_group_id = (known after apply)
+ type = "ingress"
}

# alicloud_security_group_rule.allow_80_tcp will be created
+ resource "alicloud_security_group_rule" "allow_80_tcp" {
+ cidr_ip = "0.0.0.0/0"
+ id = (known after apply)
+ ip_protocol = "tcp"
+ nic_type = "intranet"
+ policy = "accept"
+ port_range = "80/80"
+ prefix_list_id = (known after apply)
+ priority = 1
+ security_group_id = (known after apply)
+ type = "ingress"
}

# alicloud_vpc.vpc will be created
+ resource "alicloud_vpc" "vpc" {
+ cidr_block = "172.16.0.0/16"
+ create_time = (known after apply)
+ id = (known after apply)
+ ipv6_cidr_block = (known after apply)
+ ipv6_cidr_blocks = (known after apply)
+ name = (known after apply)
+ resource_group_id = (known after apply)
+ route_table_id = (known after apply)
+ router_id = (known after apply)
+ router_table_id = (known after apply)
+ secondary_cidr_blocks = (known after apply)
+ status = (known after apply)
+ user_cidrs = (known after apply)
+ vpc_name = "tf_test"
}

# alicloud_vswitch.vsw will be created
+ resource "alicloud_vswitch" "vsw" {
+ availability_zone = (known after apply)
+ cidr_block = "172.16.128.0/20"
+ create_time = (known after apply)
+ id = (known after apply)
+ ipv6_cidr_block = (known after apply)
+ ipv6_cidr_block_mask = (known after apply)
+ name = (known after apply)
+ status = (known after apply)
+ vpc_id = (known after apply)
+ vswitch_name = (known after apply)
+ zone_id = "cn-shanghai-b"
}

会让用户进行进一步确认

1
2
3
4
5
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.

Enter a value: yes

选择yes后,开始创建资源

此时登录到阿里云控制台,进行对比

专有网络已经被创建

image-20231012111205645

交换机也被创建

image-20231012111450805

实际上关于alicloud插件各种使用方法都可以参看terraform官网教程

例如关于各种资源定义可以参考:

https://registry.terraform.io/providers/aliyun/alicloud/latest/docs/resources

image-20231012142053866

创建完成后,我们通过阿里云控制台可以看到虚拟机也被成功创建【注意:这里的instance_type在实际tf文件中定义为esc.n1.tiny,所以还是和最终结果一致的】

image-20231015204302343

通过公网地址,我们通过root账户登录,去验证terraform定义的root账户密码是否正确

1
2
[root@iZuf6i3poehqbd3c5kvg23Z ~]# cat /etc/anolis-release 
Anolis OS release 8.8

实际上,terraform的销毁也比较简单,直接使用terraform destroy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@RHEL9 terraform]# terraform destroy

alicloud_security_group_rule.allow_80_tcp: Destroying... [id=sg-uf65io0p05k9udypynuu:ingress:tcp:80/80:intranet:0.0.0.0/0:accept:1]
alicloud_security_group_rule.allow_22_tcp: Destroying... [id=sg-uf65io0p05k9udypynuu:ingress:tcp:22/22:intranet:0.0.0.0/0:accept:1]
alicloud_instance.instance: Destroying... [id=i-uf6hznoncvhyrd8uetd0]
alicloud_security_group_rule.allow_443_tcp: Destroying... [id=sg-uf65io0p05k9udypynuu:ingress:tcp:443/443:intranet:0.0.0.0/0:accept:1]
alicloud_security_group_rule.allow_443_tcp: Destruction complete after 1s
alicloud_security_group_rule.allow_80_tcp: Destruction complete after 1s
alicloud_security_group_rule.allow_22_tcp: Destruction complete after 2s
alicloud_instance.instance: Still destroying... [id=i-uf6hznoncvhyrd8uetd0, 10s elapsed]
alicloud_instance.instance: Destruction complete after 12s
alicloud_security_group.default: Destroying... [id=sg-uf65io0p05k9udypynuu]
alicloud_vswitch.vsw: Destroying... [id=vsw-uf6msynhjpod1d3eptavk]
alicloud_security_group.default: Destruction complete after 1s
alicloud_vswitch.vsw: Destruction complete after 8s
alicloud_vpc.vpc: Destroying... [id=vpc-uf6wlckgy4yd01tgga8me]
alicloud_vpc.vpc: Destruction complete after 0s

Destroy complete! Resources: 7 destroyed.

由此可见terraform对于使用者的权限是有一定的要求的,一个好的工具在实现了高效率敏捷的同时,一定隐藏了大量技术部分的复杂度

5 实战二 私有云VMware vSphere8

5.1 试验环节介绍

私有云采用VMware vSphere8,存储采用vSAN,三节点,主机为DELL R740(Intel(R) Xeon(R) Gold 6230R CPU @ 2.10GHz,512G内存)

其中vCenter采用HA三节点部署,IP地址为192.168.100.100

通过一个跳板机(192.168.100.101)部署了terraform二进制可执行代码,通过该跳板机直接管理远程私有云VMware vSphere8,直接创建一个基于RHEL9的虚拟机试验

我们目的是要创建【准确的说是基于模板的虚拟机克隆操作】十台模板是RHEL9的虚拟机,固定的IP地址都有特定的规划

传统的方式一定是在web界面,右键选择克隆,等创建后,编辑参数选择特定的网络然后再进行开机,然后进入操作系统设置密码,然后配置特定的IP地址

那接下来让我们利用Terraform工具进行Iac的操作之旅

5.2 初始化

我们通过terraform官方插件,搜索vmware

image-20231017123734821

我们可以通过目录看到,官方文档对于安全、网络、虚拟机等各资源的定义都做了详细说明

image-20231017130221522

5.3 定义资源

我相信通过前面实战一的理解,对各种资源的定义基本有了大概了解,只不过对于资源中心的参数定义可能需要结合业务本身去理解了

直接上tf配置文件

再次提示:千万不要将密钥等机密信息以硬编码方式写入代码中

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
provider "vsphere" {
user = "administrator@vcenter8.cj.com.cn"
password = "xxxx"
vsphere_server = "192.168.100.100"
allow_unverified_ssl = true
}

data "vsphere_datacenter" "datacenter" {
name = "DataCenter1"
}

data "vsphere_datastore" "datastore" {
name = "vsanDatastore"
datacenter_id = data.vsphere_datacenter.datacenter.id
}

data "vsphere_compute_cluster" "cluster" {
name = "DellR740"
datacenter_id = data.vsphere_datacenter.datacenter.id
}


data "vsphere_distributed_virtual_switch" "vds" {
name = "GA"
datacenter_id = data.vsphere_datacenter.datacenter.id
}

data "vsphere_network" "network" {
name = "NET1"
distributed_virtual_switch_uuid = data.vsphere_distributed_virtual_switch.vds.id
datacenter_id = data.vsphere_datacenter.datacenter.id
}

data "vsphere_virtual_machine" "template" {
name = "RHEL9template"
datacenter_id = data.vsphere_datacenter.datacenter.id
}

resource "vsphere_virtual_machine" "vm" {
name = "tf_test"
resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
datastore_id = data.vsphere_datastore.datastore.id
num_cpus = 2
memory = 4096
firmware = "efi"
guest_id = data.vsphere_virtual_machine.template.guest_id
scsi_type = data.vsphere_virtual_machine.template.scsi_type
network_interface {
network_id = data.vsphere_network.network.id
adapter_type = data.vsphere_virtual_machine.template.network_interface_types[0]
}
disk {
label = "disk0"
size = data.vsphere_virtual_machine.template.disks.0.size
thin_provisioned = data.vsphere_virtual_machine.template.disks.0.thin_provisioned
}
clone {
template_uuid = data.vsphere_virtual_machine.template.id
customize {
linux_options {
host_name = "RHEL9-tf-test"
domain = "cj.com.cn"
}
network_interface {
ipv4_address = "172.18.3.19"
ipv4_netmask = 24
}
ipv4_gateway = "172.18.3.254"
}
}
}

5.3.1 资源参数详解

首先我们定义了6个data块,为了更好的将数据源定义和现存环境进行对应,我配上图请各位直接对比学习

1、下图描述了数据中心和集群名称

image-20231018124450294

2、下图代表了数据中心类型和数据存储类型

image-20231018124044019

3、下图描述分布式交换机和端口组

image-20231018124320116

4、创建虚拟机使用的模板

image-20231018124618948

5.4 使用

直接使用terraform plan

提示只有一个资源即vsphere_virtual_machine这个类型将被创建

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

执行terraform apply

提示信息很清晰得显示,开始一条条的对于data资源的ReadingRead complete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@RHEL9 terraform-vmware]# terraform apply
data.vsphere_datacenter.datacenter: Reading...
data.vsphere_datacenter.datacenter: Read complete after 0s [id=datacenter-3]
data.vsphere_datastore.datastore: Reading...
data.vsphere_distributed_virtual_switch.vds: Reading...
data.vsphere_compute_cluster.cluster: Reading...
data.vsphere_virtual_machine.template: Reading...
data.vsphere_datastore.datastore: Read complete after 0s [id=datastore-32]
data.vsphere_distributed_virtual_switch.vds: Read complete after 0s [id=50 1b f6 3f 14 f0 73 a9-e3 27 99 78 5b d2 9f 8b]
data.vsphere_network.network: Reading...
data.vsphere_compute_cluster.cluster: Read complete after 0s [id=domain-c8]
data.vsphere_network.network: Read complete after 0s [id=dvportgroup-44]
data.vsphere_virtual_machine.template: Read complete after 0s [id=421b5637-8363-fa19-66e2-a9563cfbd550]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create

接下来是对于创建虚拟机资源的参数提示

1
2
3
4
5
6
7
Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.

Enter a value:

输入yes后,terraform会轮询创建状态

1
2
vsphere_virtual_machine.vm: Creating...
vsphere_virtual_machine.vm: Still creating... [10s elapsed]

此时登录vSphere vCenter控制台,会发现虚拟机已经在克隆状态了

image-20231017222501482

由于集群性能比较好,实际操作下来速度还是比较快的

image-20231018131619505

对于一个虚拟机的创建来说,可能节约不了多少时间,但是如果是十个、几十个虚拟机的创建,然后基于虚拟机创建后进一步的Pass工具的自动化部署呢?

后续我们可以通过定义多个模板,并且将K8S插件一起纳入,实现一键创建K8S集群的代码

6 排错

6.1 账户余额不足

实战一种,我们采用的是后付费模式,但是即使如此当阿里云的账户余额不足100元时候,会提示InvalidAccountStatus.NotEnoughBalance错误

1
2
3
4
5
6
7
8
9
10
11
12
13

│ Error: [ERROR] terraform-provider-alicloud/alicloud/resource_alicloud_instance.go:851: Resource alicloud_instance RunInstances Failed!!! [SDK alibaba-cloud-sdk-go ERROR]:
│ SDKError:
│ StatusCode: 403
│ Code: InvalidAccountStatus.NotEnoughBalance
│ Message: code: 403, Your account does not have enough balance. request id: A5A62491-3EE3-5160-8D25-7E51D26624FD
│ Data: {"Code":"InvalidAccountStatus.NotEnoughBalance","HostId":"ecs.cn-shanghai.aliyuncs.com","Message":"Your account does not have enough balance.","Recommend":"https://api.aliyun.com/troubleshoot?q=InvalidAccountStatus.NotEnoughBalance\u0026product=Ecs\u0026requestId=A5A62491-3EE3-5160-8D25-7E51D26624FD","RequestId":"A5A62491-3EE3-5160-8D25-7E51D26624FD"}


│ with alicloud_instance.instance,
│ on aliyun.tf line 62, in resource "alicloud_instance" "instance":
│ 62: resource "alicloud_instance" "instance" {

我们根据提示访问https://api.aliyun.com/troubleshoot?q=InvalidAccountStatus.NotEnoughBalance\u0026product=Ecs\u0026requestId=A5A62491-3EE3-5160-8D25-7E51D26624FD

阿里云通过一套可视化的链路跟踪,输入RequestId去跟踪各微服务链路调用环节中出现的问题,包括API名称,网关处理时间,以及Code返回内容

当然对于Code返回内容可能需要结合自身经验进行初步判断,如果还是不能准确定位可能就需要请阿里客户接入了

image-20231015203851753

解决办法:通过阿里云账户充值,满足余额100元以上,问题解决

论技术那加强,阿里巴巴,微服务的链路跟踪的体验度还是非常好的

6.2 资源参数定义错误

在实际定义资源中,我们对于资源的定义可以多参考terraform的provider插件的官网文档

但对于资源中各种资源呢参数的定义,是需要输入正确的字符串值,否则terraform在调用provider插件背后还是将CURD指令翻译成远程资源的API

例如我们将region值填写错误,实际应为cn-shanghai-b,而我们定义为cn-shanghai

1
2
3
4
5
6
7
8
9
10
11
12
13

│ Error: [ERROR] terraform-provider-alicloud/alicloud/resource_alicloud_vswitch.go:151: Resource alicloud_vswitch CreateVSwitch Failed!!! [SDK alibaba-cloud-sdk-go ERROR]:
│ SDKError:
│ StatusCode: 400
│ Code: ResourceNotAvailable
│ Message: code: 400, Resource you requested is not available in this region or zone. request id: 1B7994FB-5E20-5F53-9925-80B2935A7529
│ Data: {"Code":"ResourceNotAvailable","HostId":"vpc.cn-shanghai.aliyuncs.com","Message":"Resource you requested is not available in this region or zone.","Recommend":"https://api.aliyun.com/troubleshoot?q=ResourceNotAvailable\u0026product=Vpc\u0026requestId=1B7994FB-5E20-5F53-9925-80B2935A7529","RequestId":"1B7994FB-5E20-5F53-9925-80B2935A7529"}


│ with alicloud_vswitch.vsw,
│ on aliyun.tf line 15, in resource "alicloud_vswitch" "vsw":
│ 15: resource "alicloud_vswitch" "vsw" {

同样我们使用阿里云的可视化链路跟踪工具,可以看到,这里Code返回ResourceNotAvalible

image-20231012105830549

API请求的信息中ZoneIDcn-shanghai,而实际应为cn-shanghai-b

image-20231012105925428

至此故障定位很清晰,即terraform代码定义中region id应为cn-shanghai-b,修改后故障排除

6.3 vmware创建虚拟后无法打开虚拟机电源

使用terraform成功创建虚拟机后,提示无法打开虚拟机电源,登陆到vSphere控制台上,查看日志提示ACPI主板布局需要EFI

image-20231017222632740

另外近期任务的日志里也清晰对无法启动虚拟机进行了故障定位

image-20231017222650908

故障解决办法就是,在resource "vsphere_virtual_machine" "vm"中添加firmware = "efi"

6.4 无法在创建虚拟机过程中直接配置IP地址

使用Terraform成功创建虚拟机后,被创建的虚拟机啊的IP地址无法按照预定配置

实际上关键点在于,在于创建虚拟机的模板,必须实现安装好两个软件包,即perlopen-vm-tools

1
2
dnf install -y perl
dnf install -y open-vm-tools

通过将已经是模板,右键直接选择转换成虚拟机,开机后安装上述软件包

image-20231017225417592

软件安装完成后,再对该虚拟机关键后右键进行模板-转换为模板的操作

7 最后

本文只是terraform工具的初步体验,terraform还有很多配置文件、语法模块、命令行参数、代码风格规范等等,这些都可以进一步参考官网学习

官网上还提供了认证培训的课程,https://www.hashicorp.com/certification/terraform-associate

最后terraform并不是银弹,简单的声明式代码并不具备图灵完备,也还会受局限于资源提供方所提供的API功能,但是terraform背后IaC的理念、Devops的理念都是值得学习和思考的