新钛云服已为您服务1456天
(万字长文,建议 PC 端观看效果更佳)
Terraform 是什么
Terraform(https://www.terraform.io/)是 HashiCorp 旗下的一款开源(Go 语言开发)的 DevOps 基础架构资源管理运维工具,可以看下对应的 DevOps 工具链:
Terraform 可以安全高效的构建、更改和合并多个云厂商的各种服务资源,当前支持有阿里云、AWS、微软 Azure、Vmware、Google Cloud Platform 等多个云厂商云产品的资源创建。
Write, Plan, and Create Infrastructure as Code
Terraform 通过模板配置文件定义所有资源类型(有如主机,OS,存储类型,中间件,网络 VPC,SLB,DB,Cache 等)和资源的数量、规格类型、资源创建依赖关系,基于资源厂商的 OpenAPI 快速创建一键创建定义的资源列表,同时也支持资源的一键销毁。
顺便介绍一下 HashiCorp 这家公司的其他产品:
Vagrant Vagrant by HashiCorp
Consul HashiCorp Consul - Connect and Secure Any Service
Vault HashiCorp Vault - Manage Secrets & Protect Sensitive Data
Nomad HashiCorp Nomad Enterprise
Packer Packer by HashiCorp
Terraform 初体验
接下来,我们就安装并体验一下 Terraform。
安装
CentOS 7 安装
接下来在 CentOS 7 上面进行安装,如下:
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo yum -y install terraform
验证版本信息:
[root@mybox02 ~]# terraform version
Terraform v1.0.2
on linux_amd64
MacOS 安装
如果是在 MacOS 上面安装,则执行如下命令:
$ brew install terraform
验证版本信息:
$ terraform version
Terraform v1.0.3
on darwin_amd64
获取命令行帮助
如何获取命令行帮助呢?
# 获取帮助信息,查看 Terraform 支持哪些子命令及参数
terraform -help
# 查看具体某个子命令的帮助信息
terraform -help plan
# 开启命令行补全
terraform -install-autocomplete
创建一台阿里云 ECS 实例
准备子账号
创建 RAM 子账户,子账户只能通过 OpenAPI 的形式访问云上的资源,而且不能赋予所有的权限,只赋予指定的权限,如 ECS、RDS、SLB 等具体的权限。
推荐使用环境变量的方式存放身份认证信息:
export ALICLOUD_ACCESS_KEY="********"
export ALICLOUD_SECRET_KEY="*************"
export ALICLOUD_REGION="cn-shanghai"
一段代码
下面是一段测试的代码 main.tf
,其主要功能是在阿里云上创建 VPC、vSwitch、安全组、ECS 实例,最后输出 ECS 的外网 IP。代码如下:
resource "alicloud_vpc" "vpc" {
name = "tf_test_foo"
cidr_block = "172.16.0.0/12"
}
resource "alicloud_vswitch" "vsw" {
vpc_id = alicloud_vpc.vpc.id
cidr_block = "172.16.0.0/21"
availability_zone = "cn-shanghai-b"
}
resource "alicloud_security_group" "default" {
name = "default"
vpc_id = alicloud_vpc.vpc.id
}
resource "alicloud_security_group_rule" "allow_all_tcp" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "intranet"
policy = "accept"
port_range = "1/65535"
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.n2.small"
system_disk_category = "cloud_efficiency"
image_id = "ubuntu_18_04_64_20G_alibase_20190624.vhd"
instance_name = "test_foo"
vswitch_id = alicloud_vswitch.vsw.id
internet_max_bandwidth_out = 1
password = "yourPassword"
}
output "public_ip" {
value = alicloud_instance.instance.public_ip
}
我们通过一个 main.tf 文件(只需要是 .tf
文件)定义了 ECS(镜像、实例类型)、VPC(CIDR、VPC Name)、安全组等,通过 Terraform 对资源配置参数做解析,调用阿里云 OpenAPI 进行资源校验于创建,同时把整个资源创建状态化到一个 .tf.state
文件中,基于该文件则可以得知资源创建的所有信息,包括资源数量调整,规格调整,实例变更都依赖这种非常重要的文件。
查看结果
$ terraform show
有何感想
我们通过代码完成了基础设施的创建,而且创建出来的资源就是按照我们声明文件中那样描述的那样。这其实就是基础设施即代码的一种技术实现。
基础设施即代码是一种使用新的技术来构建和管理动态基础设施的方式。它把基础设施、工具和服务以及对基础设施的管理本身作为一个软件系统,采纳软件工程实践以结构化的安全的方式来管理对系统的变更。
基础设施即代码有四项关键原则:
再生性:环境中的任何元素可以轻松复制。
一致性:无论何时,创建的环境各个元素的配置是完全相同的。
快速反馈:能够频繁、容易地进行变更,并快速知道变更是否正确。
可见性:所有对环境的变更应该容易理解、可审计、受版本控制。
接下来的章节就具体看看 Terraform 是如何编程、及扩展 Terraform。
Terraform 编程
如果在编写代码时,遇到什么问题,可以在官方网站寻找答案。官方网站是最好的学习资源,没有之一。
文档链接为:https://www.terraform.io/language。接下来就看一个示例。
一个示例
变量定义:
# 列表的例子
variable "list_example" {
description = "An example of a list in Terraform"
type = "list"
default = [1, 2, 3]
}
# 字典的例子
variable "map_example" {
description = "An example of a map in Terraform"
type = "map"
default = {
key1 = "value1"
key2 = "value2"
key3 = "value3"
}
}
# 如果不指定类型,默认是字符串
variable "server_port" {
description = "The port the server will use for HTTP requests"
}
一个例子:
$ tree -L 1 .
.
├── madlibs
├── madlibs.tf
├── madlibs.zip
├── templates
├── terraform.tfstate
├── terraform.tfstate.backup
└── terraform.tfvars
2 directories, 5 files
看下 terraform.tfvars
文件的内容:
[root@blog ch03]# cat terraform.tfvars
words = {
nouns = ["army", "panther", "walnuts", "sandwich", "Zeus", "banana", "cat", "jellyfish", "jigsaw", "violin", "milk", "sun"]
adjectives = ["bitter", "sticky", "thundering", "abundant", "chubby", "grumpy"]
verbs = ["run", "dance", "love", "respect", "kicked", "baked"]
adverbs = ["delicately", "beautifully", "quickly", "truthfully", "wearily"]
numbers = [42, 27, 101, 73, -5, 0]
}
及 madlibs.tf
文件的内容
[root@blog ch03]# cat madlibs.tf
terraform {
required_version = ">= 0.15"
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.0"
}
local = {
source = "hashicorp/local"
version = "~> 2.0"
}
archive = {
source = "hashicorp/archive"
version = "~> 2.0"
}
}
}
variable "words" {
description = "A word pool to use for Mad Libs"
type = object({
nouns = list(string),
adjectives = list(string),
verbs = list(string),
adverbs = list(string),
numbers = list(number),
})
validation {
condition = length(var.words["nouns"]) >= 10
error_message = "At least 10 nouns must be supplied."
}
}
variable "num_files" {
type = number
description = "(optional) describe your variable"
default = 100
}
locals {
uppercase_words = { for k, v in var.words : k => [for s in v : upper(s)] }
}
resource "random_shuffle" "random_nouns" {
count = var.num_files
input = local.uppercase_words["nouns"]
}
resource "random_shuffle" "random_adjectives" {
count = var.num_files
input = local.uppercase_words["adjectives"]
}
resource "random_shuffle" "random_verbs" {
count = var.num_files
input = local.uppercase_words["verbs"]
}
resource "random_shuffle" "random_adverbs" {
count = var.num_files
input = local.uppercase_words["adverbs"]
}
resource "random_shuffle" "random_numbers" {
count = var.num_files
input = local.uppercase_words["numbers"]
}
locals {
templates = tolist(fileset(path.module, "templates/*.txt"))
}
resource "local_file" "mad_libs" {
count = var.num_files
filename = "madlibs/madlibs-${count.index}.txt"
content = templatefile(element(local.templates, count.index),
{
nouns = random_shuffle.random_nouns[count.index].result
adjectives = random_shuffle.random_adjectives[count.index].result
verbs = random_shuffle.random_verbs[count.index].result
adverbs = random_shuffle.random_adverbs[count.index].result
numbers = random_shuffle.random_numbers[count.index].result
})
}
data "archive_file" "mad_libs" {
depends_on = [
local_file.mad_libs
]
type = "zip"
source_dir = "${path.module}/madlibs"
output_path = "${path.cwd}/madlibs.zip"
}
如何引用变量的值?
var.<VARIABLE_NAME>
.
注意事项
Terraform 不支持自定义函数。我们只能使用 Terraform 内置的大约 100 个函数进行编程。
Repeat Yourself vs. Don't Repeat Yourself (DRY)
在软件工程中,是不提倡 DRY 的。可是现实中,我们到处可以看到 Ctrl-C
及 Ctrl-V
的这样的编程范式(:P)。
下面两个代码示例,哪个更好一点?
从上图来看,左边的代码结构是最优的,其符合 DRY 原则;而右边的代码结构则是符合 Ctrl-C
、Ctrl-V
这种模式。针对上述两种场景,我们给出以下建议:
当我们的环境没有差异或差异比较小,建议使用左边的代码结构;
当我们的环境差异比较大的时候,就只能选择右边的代码结构了;
接下来我们看看 Terraform 是如何解决上述问题的。
使用 workspace 以复用代码
对于同样的配置文件,workspace
功能允许我们可以有多个状态文件。这就意味着,我们不需要通过复制、粘贴代码,就可以实现多环节部署。每个 workspace
拥有自己的变量及环境信息。如下图所示:
我们之前就已经在使用 workspace
了,尽管我们并没有意识到这一点。当执行 terraform init
的时候,Terraform
就已经创建了一个默认的 workspace
并切换到该 workspace
下了。可以验证一下:
[root@blog ch03]# terraform workspace list
* default
多环境部署
接下来我们使用 Terraform 的 workspace 特性进行一个多环境的部署。
一个例子:
[root@blog ch06]# tree .
.
├── environments
│ ├── dev.tfvars
│ └── prod.tfvars
├── main.tf
└── terraform.tfstate.d
├── dev
│ ├── terraform.tfstate
│ └── terraform.tfstate.backup
└── prod
└── terraform.tfstate
4 directories, 6 files