Terraform是一种部署技术,任何想要通过基础设施即代码(Infrastructure as Code,IaC)方法来置备和管理基础设施的人,都可以使用这种技术。基础设施指的主要是基于云的基础设施,不过从技术上讲,任何能够通过应用程序编程接口(Application Programming Interface,API)进行控制的东西都可以算作基础设施。基础设施即代码是通过机器可读的定义文件来管理和置备基础设施的过程的。我们使用IaC来自动完成原本要由人手动完成的过程。

所谓置备,指的是基础设施部署,而不是配置管理,后者主要处理应用程序交付,特别是在虚拟机(Virtual Machine,VM)上交付。Ansible、Puppet、SaltStack和Chef等配置管理(Configuration Management,CM)工具已经存在多年,非常流行。Terraform并没有取代这些工具,至少不会完全取代,因为基础设施置备和配置管理在本质上是不同的问题。即使如此,Terraform也会提供原本只有CM工具会提供的一些功能,许多公司在采用了Terraform之后,发现自己并不需要CM工具。

Terraform的基本原则是,它允许编写人类可读的配置代码来定义IaC。借助配置代码,你可以把可重复的、短暂的、一致的环境部署到公有云、私有云和混合云上的供应商(参见图1.1)。

本章会先介绍Terraform相对于其他IaC技术的优缺点,以及它如何从这些技术中脱颖而出,然后通过把一个服务器部署到AWS,并使用Terraform的一些动态特性来改进它,演示Terraform的“Hello World!”示例。

图1.1 Terraform可以把基础设施部署到任何云或者混合云中

1.1 Terraform的优点

近来有大量关于Terraform的宣传,但这种宣传有理有据吗?Terraform并不是唯一的IaC技术,还有其他许多工具也能完成同样的工作。软件部署是利润颇丰的市场领域,Terraform为什么能够在这个领域与Amazon、Microsoft和Google等公司的技术竞争呢?有6个关键特征让Terraform与众不同,给它带来了竞争优势。

  • 置备工具:部署基础设施,而不只是应用程序。
  • 易于使用:适合我们这些不是天才的人使用。
  • 免费且开源:谁不喜欢免费的东西呢?
  • 声明式:说出你想要的结果,而不是说出如何实现这个结果。
  • 云无关:使用相同的工具部署到任意云。
  • 表达能力强且可扩展:不受语言的限制。

表1.1将Terraform与其他IaC工具进行了对比。

表1.1 Terraform与其他IaC工具的对比

名称

关键特征

置备工具

易于使用

免费、开源

声明式

云无关

表达能力强、可扩展

Ansible

×

×

×

×

Chef

×

×

×

×

Puppet

×

×

×

×

SaltStack

×

×

×

×

×

Terraform

×

×

×

×

×

×

Pulumi

×

×

×

×

AWS CloudFormation

×

×

×

GCP Deployment Manager

×

×

×

Azure Resource Manager

×

技术对比

从技术上讲,Pulumi最接近Terraform,唯一的区别在于它不是声明式的。Pulumi团队认为这是Pulumi相较于Terraform的优势,但Terraform也有一个云开发工具包(Cloud Development Kit,CDK),允许实现相同的功能。

Terraform的设计受到了AWS CloudFormation的启发,并且与GCP Deployment Manager和Azure Resource Manager有很相近的地方。那些技术虽然也不错,但都不是与具体云无关的技术,也都不是开源的。它们只能用于特定的云供应商,并且一般不如Terraform简洁和灵活。

Ansible、Chef、Puppet和SaltStack都是配置管理工具,而不是基础设施置备工具。它们解决的问题类别与Terraform有些区别,不过也存在重叠的地方。

1.1.1 置备工具

Terraform是一种基础设施置备工具,而不是配置管理工具。置备工具部署和管理基础设施,而配置管理工具(如Ansible、Puppet、SaltStack和Chef)将软件部署到现有服务器上。一些配置管理工具也能够执行一定程度的基础设施置备,但不如Terraform,因为它们并不是为这类任务设计的。

配置管理工具和置备工具之间的区别主要在于理念。配置管理工具常用于管理可变基础设施,而Terraform和其他置备工具常用于管理不可变基础设施。

可变基础设施意味着在现有服务器上执行软件更新。不可变基础设施则不关心现有服务器,它把基础设施视为用后即可丢弃的商品。这两种范式之间的区别可归结为复用思想与用后丢弃思想的区别。

1.1.2 易于使用

即使是非程序员,也可以快速、轻松地学会Terraform的基础知识。到第4章结束时,你将具备中级Terraform用户必备的技能。细想一下,这简直让人难以置信。当然,要精通Terraform就是另外一回事了,不过对于大部分技能都是如此。

Terraform之所以如此易用,主要原因在于其代码是用一种称作HashiCorp Configuration Language(HCL)的领域特定的配置语言编写的。HashiCorp开发了这种语言,用来替代更加冗长的JSON和XML等配置语言。HCL试图在人类可读性和机器可读性之间达到一种平衡,并受到了这个领域中一些早期尝试(如libucl和Nginx配置)的影响。HCL与JSON完全兼容,这意味着HCL能够完全转换为JSON,反之亦然。这就使得与Terraform之外的系统进行互操作或者动态生成配置代码变得十分简单。

1.1.3 免费且开源的软件

Terraform的引擎称作Terraform core,这是一款免费且开源的软件,通过Mozilla Public License v2.0提供。该许可规定,任何人都可以出于个人目的和商业目的使用、分发或修改软件。免费这一点很好,因为这意味着你在使用Terraform时不必担心会承担额外的费用。另外,它使得产品及其工作方式对用户来说变得透明。

Terraform没有提供高级版本,但提供了商业解决方案和企业解决方案(Terraform Cloud和Terraform Enterprise),可成规模运行Terraform。第6章将介绍这些解决方案,在第12章中,我们将自己实现一个Terraform Enterprise。

1.1.4 声明式编程

声明式编程指的是表达计算逻辑(做什么),但不描述控制流(怎么做)。你不必编写一步步执行的指令,只要描述自己想要的结果即可。数据库查询语言(SQL)、函数式编程语言(Haskell、Clojure)、配置语言(XML、JSON)和大部分IaC工具(Ansible、Chef、Puppet)都是声明式编程语言的示例。

声明式编程语言是与命令式(或过程式)编程相对的。命令式语言使用条件分支、循环和表达式来控制系统流程、保存状态和执行命令。几乎所有传统编程语言(如Python、Java、C等)都是命令式编程语言。

注意 声明式编程关注的是结果,而不是过程。命令式编程关注的是过程,而不是结果。

1.1.5 云无关

云无关指的是能够使用一组相同的工具和工作流,无缝运行在任意云平台上。Terraform是云无关的,使用Terraform把基础设施部署到AWS与部署到GCP、Azure甚至私有数据中心一样简单(参见图1.2)。云无关很重要,因为这意味着你不会被局限于特定的云供应商,也不需要在每次改变云供应商时学习一种全新的技术。

图1.2 使用Terraform同时部署到多个云

Terraform通过提供程序(provider)与不同的云集成。提供程序是Terraform插件,用于与外部API进行交互。每个云供应商都会维护自己的Terraform提供程序,使Terraform能够管理该云中的资源。提供程序是使用Go语言编写的,并作为二进制文件分发到Terraform注册表上。它们负责进行身份验证、发出API请求以及处理超时和错误。在这个注册表中,有数百个已经发布的提供程序,它们协同起来,使你能够管理数千种不同的资源。第11章将会对此进行介绍,你甚至可以编写自己的Terraform提供程序。

1.1.6 表达能力强且高度可扩展

与其他声明式IaC工具相比,Terraform的表达能力强,且高度可扩展。通过使用条件语句、for表达式、指令、模板文件、动态块、变量和许多内置函数,我们可以轻松地编写代码来实现自己的目的。表1.2从技术的角度对比了Terraform和AWS CloudFormation(催生Terraform的技术)。

表1.2 Terraform和AWS CloudFormation的技术对比

名称

语言特性

其他特性

本身提供的函数

条件语句

for循环

类型

支持插件

模块化

等待条件

Terraform

115个

字符串、数字、列表、映射、布尔值、对象、复杂类型

AWS
CloudFormation

11个

字符串、数字、列表

有限程度

1.2 “Hello Terraform!”

本节介绍Terraform的一种经典用例——在AWS上部署一个虚拟机(EC2实例)。我们将使用Terraform的AWS提供程序来代表我们发出API调用和部署EC2实例。完成部署后,我们将让Terraform销毁该实例,避免服务器一直运行,造成越来越多的费用。图1.3显示了该操作的部署流程。

这个场景有一个先决条件——你必须安装了Terraform 0.15.X,并具有AWS的访问凭据。部署项目的步骤如下所示。

(1)编写Terraform配置文件。

(2)配置AWS提供程序。

(3)使用terraform init初始化Terraform。

(4)使用terraform apply部署EC2实例。

(5)使用terraform destroy进行清理。

图1.4演示了“Hello Terraform!”部署的工作流程。

图1.3 使用Terraform在AWS上部署一个EC2实例的架构

图1.4 “Hello Terraform!”的部署流程

1.2.1 编写Terraform配置

Terraform通过读取配置文件来部署基础设施。要告诉Terraform部署一个EC2实例,需要使用代码来声明该EC2实例。为此,先要创建一个新文件,将其命名为main.tf,并添加代码清单1.1中的内容。.tf扩展名表示这是一个Terraform配置文件。Terraform在运行时,将读取工作目录中所有具有.tf扩展名的文件,并把它们连接起来。

注意 本书中的所有代码均可在GitHub上通过搜索“
terraform-in-action/manning-code”获取。

代码清单1.1 main.tf的内容

resource "aws_instance" "helloworld" {    ⇽---  声明一个名为“HelloWorld”的aws_instance资源
  ami           = "ami-09dd2e08d601bff67"  ⇽--- EC2实例的特性 
  instance_type = "t2.micro"
  tags = {
    Name = "HelloWorld"
  }
}

注意 此Amazon机器映像(Amazon Machine Image,AMI)仅对us-west-2地区有效。

代码清单1.1中的代码声明,我们希望Terraform置备一个t2.micro AWS EC2实例,使其具有Ubuntu AMI和一个名称标签。对比下面给出的等效的CloudFormation代码,可以看到Terraform代码要清晰得多,也简洁得多。

{
    "Resources": {
        "Example": {
            "Type": "AWS::EC2::Instance",
            "Properties": {
                "ImageId": "ami-09dd2e08d601bff67",
                "InstanceType": "t2.micro",
                "Tags": [
                    {
                        "Key": "Name",
                        "Value": "HelloWorld"
                    }
                ]
            }
        }
    }
}

这个EC2代码块是Terraform资源的一个示例。在Terraform中,资源是最重要的元素,因为它们置备虚拟机、负载均衡器、NAT网关等基础设施。资源被声明为HCL对象,具有resource类型和两个标签。第一个标签指定了要创建的资源的类型,第二个标签是资源的名称。名称并没有特别的意义,只用来在给定模块作用域内引用该资源。类型与名称合起来构成资源标识符,每个资源的标识符都是唯一的。图1.5显示了Terraform资源块的语法。

图1.5 资源块的语法

每个资源都有输入和输出。输入称作实参,输出称作特性。实参通过资源进行传递,也可作为资源特性使用。另外,资源还有计算特性,但只有在创建了资源后才能使用它们。计算特性包含计算得到的关于管理资源的信息。图1.6显示了aws_instance资源的实参、特性和计算特性的示例。

图1.6 aws_instance资源的实参、特性和计算特性的示例

1.2.2 配置AWS提供程序

接下来,我们需要配置AWS提供程序。AWS提供程序负责理解API交互、发出经过身份验证的请求,以及为Terraform提供资源。下面通过添加一个provider块来配置AWS提供程序。按照代码清单1.2更新main.tf中的代码。

代码清单1.2 main.tf

provider "aws" {    ⇽---  声明AWS提供程序
![箭头08{5%}](/api/storage/getbykey/original?key=22031b3595d646878b29)  region = "us-west-2"    ⇽---  配置部署地区
}

resource "aws_instance" "helloworld" {
  ami           = "ami-09dd2e08d601bff67"
  instance_type = "t2.micro"
  tags = {
    Name = "HelloWorld"
  }
}

注意 在置备基础设施之前,需要先获得AWS凭据。凭据可以存储到凭据文件中或者环境变量中。

与资源不同,提供程序只有一个标签Name。这是该提供程序在Terraform注册表中发布时使用的正式名称(如“aws”代表AWS,“google”代表GCP,“azurerm”代表Azure)。提供程序块的语法如图1.7所示。

图1.7 提供程序块的语法

注意 Terraform注册表是一个全球商店,用来分享版本化提供程序的二进制文件。当Terraform初始化时,会从该注册表自动查找和下载任何必要的提供程序。

提供程序没有输出,只有输入。通过传递输入(或配置实参)给provider块,可以配置提供程序。配置实参包括服务端点URL、地区、提供程序版本、通过API身份验证所需的任何凭据等。图1.8演示了其注入过程。

图1.8 当发出API调用时,配置的提供程序如何把凭据注入aws_instance中

通常,你不会想要把凭据信息作为纯文本传递给提供程序,特别是以后要把这些代码签入版本控制系统的时候更是如此。因此,许多提供程序允许从环境变量或者共享凭据文件中读取凭据。如果对凭据管理感兴趣,建议阅读第13章,详细了解这个主题。

1.2.3 初始化Terraform

在让Terraform部署EC2实例之前,我们首先必须初始化工作空间。尽管我们已经声明了AWS提供程序,但是Terraform仍然需要从Terraform注册表下载和安装二进制文件。至少需要为所有工作空间执行一次初始化。

运行terraform init命令可以初始化Terraform。运行该命令将看到如下输出。

$ terraform init

Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Installing hashicorp/aws v3.28.0...    ⇽---  Terraform获取AWS提供程序的最新版本
- Installed hashicorp/aws v3.28.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.

_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.4 部署EC2实例

现在,我们就准备好使用Terraform部署EC2实例了。这需要执行下面的terraform apply命令。

警告 完成此操作后会启用EC2和CloudWatch Logs,这可能会导致对你的AWS账户收费。

$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.helloworld will be created
  + resource "aws_instance" "helloworld" { 
      + ami                                = "ami-09dd2e08d601bff67"    ⇽---  ami特性
      + arn                                = (known after apply) 
      + associate_public_ip_address        = (known after apply)
      + availability_zone                 = (known after apply) 
      + cpu_core_count                    = (known after apply)
      + cpu_threads_per_core               = (known after apply) 
      + get_password_data                  = false
      + host_id                           = (known after apply) 
*      + id                                 = (known after apply)
      + instance_state                    = (known after apply) 
*      + instance_type                      = "t2.micro"    ⇽---  instance_type特性
      + ipv6_address_count                 = (known after apply) 
      + ipv6_addresses                    = (known after apply)
      + key_name                           = (known after apply) 
      + network_interface_id               = (known after apply)
      + outpost_arn                        = (known after apply) 
      + password_data                      = (known after apply)
      + placement_group                    = (known after apply) 
      + primary_network_interface_id     = (known after apply)
      + private_dns                      = (known after apply) 
      + private_ip                       = (known after apply)
      + public_dns                       = (known after apply) 
      + public_ip                        = (known after apply)
      + security_groups                  = (known after apply) 
      + source_dest_check                 = true
      + subnet_id                        = (known after apply) 
      + tags                             = {    ⇽---  Tags特性
          +  "Name"                      = "HelloWorld"
        }
      + tenancy                          = (known after apply) 
      + volume_tags                      = (known after apply)
      + vpc_security_group_ids           = (known after apply) 

      + ebs_block_device {
          + delete_on_termination        = (known after apply) 
          + device_name                  = (known after apply)
          + encrypted                    = (known after apply) 
          + iops                         = (known after apply)
          + kms_key_id                   = (known after apply) 
          + snapshot_id                  = (known after apply)
          + volume_id                    = (known after apply) 
          + volume_size                  = (known after apply)
          + volume_type                  = (known after apply)
        }

      + ephemeral_block_device { 
          + device_name                  = (known after apply)
          + no_device                    = (known after apply) 
          + virtual_name                 = (known after apply)
        }

      + metadata_options { 
          + http_endpoint                  = (known after apply)
          + http_put_response_hop_limit    = (known after apply) 
          + http_tokens                   = (known after apply)
        }

      + network_interface { 
          + delete_on_termination        = (known after apply)
          + device_index                  = (known after apply) 
          + network_interface_id          = (known after apply)
        }

      + root_block_device { 
          + delete_on_termination          = (known after apply)
          + device_name                    = (known after apply) 
          + encrypted                     = (known after apply)
          + iops                          = (known after apply) 
          + kms_key_id                    = (known after apply)
          + volume_id                     = (known after apply) 
          + volume_size                   = (known after apply)
          + volume_type                   = (known after apply)
        }
  }
*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:     ⇽---  手动批准步骤

提示 如果收到错误“No Valid Credentials Sources Found”,说明Terraform无法通过AWS的身份验证。

CLI输出称为执行计划,描述了Terraform计划执行哪些操作来得到人们期望的状态。在继续操作前,作为一种健全性检查,检查执行计划是一个好主意。除非在拼写时出错,否则这里不应有什么奇怪的地方。检查完执行计划后,通过在命令行输入yes批准执行。

一两分钟后(置备EC2实例大概需要这么长时间),apply即成功完成。下面是一些示例输出。

aws_instance.helloworld: Creating...
aws_instance.helloworld: Still creating... [10s elapsed]
aws_instance.helloworld: Still creating... [20s elapsed]
aws_instance.helloworld: Creation complete after 25s [id=i-070098fcf77d93c54]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed. 

要验证资源已被创建,你可以在AWS的EC2控制台找到它,如图1.9所示。注意,此实例位于us-west-2地区,因为我们在提供程序中就是这么设置的。

图1.9 AWS控制台中的EC2实例

资源的状态信息存储在一个名为terraform.tfstate的文件中。不要被扩展名.tfstate误导,它其实就是一个JSON文件。使用terraform show命令可以从状态文件输出人类可读的输出,这使得列举Terraform管理的资源的信息非常方便。下面是一条terraform show命令的执行结果。

$ terraform show
# aws_instance.helloworld:
resource "aws_instance" "helloworld" {
    ami                                = "ami-09dd2e08d601bff67"
    arn                                =
    ➥"arn:aws:ec2:us-west-2:215974853022:instance/i-070098fcf77d93c54"
    associate_public_ip_address        = true
    availability_zone                  = "us-west-2a"
    cpu_core_count                     = 1
    cpu_threads_per_core               = 1
    disable_api_termination            = false
    ebs_optimized                      = false
    get_password_data                  = false
    hibernation                        = false
   id                                 = "i-070098fcf77d93c54"    ⇽---  id是一个重要的计算特性
    instance_state                     = "running"
    instance_type                      = "t2.micro"
    ipv6_address_count                 = 0
    ipv6_addresses                     = []
    monitoring                         = false
    primary_network_interface_id     = "eni-031d47704eb23eaf0"
    private_dns                      =
    ➥"ip-172-31-25-172.us-west-2.compute.internal"
    private_ip                       = "172.31.25.172"
    public_dns                       =
    ➥"ec2-52-24-28-182.us-west-2.compute.amazonaws.com"
    public_ip                        = "52.24.28.182"
    secondary_private_ips            = []
    security_groups                  = [
        "default",
    ]
    source_dest_check                = true
    subnet_id                        = "subnet-0d78ac285558cff78"
    tags                             = {
        "Name"                       = "HelloWorld"
    }
    tenancy                          = "default"
    vpc_security_group_ids           = [
        "sg-0d8222ef7623a02a5",
    ]

    credit_specification {
        cpu_credits = "standard"
    }

    enclave_options {
        enabled = false
    }

    metadata_options {
        http_endpoint                  = "enabled"
        http_put_response_hop_limit    = 1
        http_tokens                    = "optional"
    }

    root_block_device {
        delete_on_termination        = true
        device_name                  = "/dev/sda1"
        encrypted                    = false
        iops                         = 100
        tags                         = {}
        throughput                   = 0
        volume_id                    = "vol-06b149cdd5722d6bc"
        volume_size                  = 8
        volume_type                  = "gp2"
    }
}

这里的特性远多于我们一开始在资源块中设置的特性,这是因为aws_instance中的特性大部分是可选特性或计算特性。通过设置可选实参,你可以自定义aws_instance。如果你想知道有哪些可选实参,可以参阅AWS的提供程序文档。

1.2.5 销毁EC2实例

现在是时候跟EC2实例说再见了。当不再使用基础设施时,应该销毁它们,因为在云中运行基础设施是要收费的。Terraform提供了一个特殊命令——terraform destroy,用于销毁全部资源。当运行此命令时,Terraform将会给出提示,要求你手动确认销毁操作。

$ terraform destroy
aws_instance.helloworld: Refreshing state... [id=i-070098fcf77d93c54]

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

Terraform will perform the following actions:

  # aws_instance.helloworld will be destroyed
  - resource "aws_instance" "helloworld" {
      - ami                              = "ami-09dd2e08d601bff67" -> null
      - arn                              = "arn:aws:ec2:us-west-2:215974853022:
        ➥instance/i-070098fcf77d93c54" -> null
      - associate_public_ip_address      = true -> null
      - availability_zone                = "us-west-2a" -> null
      - cpu_core_count                   = 1 -> null
      - cpu_threads_per_core             = 1 -> null
      - disable_api_termination          = false -> null
      - ebs_optimized                    = false -> null
      - get_password_data                = false -> null
      - hibernation                      = false -> null
      - id                               = "i-070098fcf77d93c54" -> null
      - instance_state                   = "running" -> null
      - instance_type                    = "t2.micro" -> null
      - ipv6_address_count               = 0 -> null
      - ipv6_addresses                   = [] -> null
      - monitoring                       = false -> null
      - primary_network_interface_id     = "eni-031d47704eb23eaf0" -> null
      - private_dns                      =
        ➥"ip-172-31-25-172.us-west-2.compute.internal" -> null
      - private_ip                       = "172.31.25.172" -> null
      - public_dns                       =
        ➥"ec2-52-24-28-182.us-west-2.compute.amazonaws.com" -> null
      - public_ip                        = "52.24.28.182" -> null
      - secondary_private_ips            = [] -> null
      - security_groups                  = [
          - "default",
        ] -> null
      - source_dest_check                = true -> null
      - subnet_id                        = "subnet-0d78ac285558cff78" -> null
      - tags = {
          - "Name"                       = "HelloWorld"
        } -> null
      - tenancy                          = "default" -> null
      - vpc_security_group_ids           = [
          - "sg-0d8222ef7623a02a5",
        ] -> null

      - credit_specification {
          - cpu_credits = "standard" -> null
        }

      - enclave_options {
          - enabled = false -> null
        }

      - metadata_options {
          - http_endpoint                  = "enabled" -> null
          - http_put_response_hop_limit     = 1 -> null
          - http_tokens                    = "optional" -> null
        }

      - root_block_device {
          - delete_on_termination = true -> null
          - device_name           = "/dev/sda1" -> null
          - encrypted             = false -> null
          - iops                  = 100 -> null
          - tags                  = {} -> null
          - throughput            = 0 -> null
          - volume_id             = "vol-06b149cdd5722d6bc" -> null
          - volume_size           = 8 -> null
          - volume_type           = "gp2" -> null
        }
   }

*Plan: 0 to add, 0 to change, 1 to destroy.    ⇽---  Terraform计划采取的操作的摘要

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: 

警告 不要手动编辑或删除terraform.tfstate文件,这一点很重要,否则Terraform将无法跟踪其管理的资源。

销毁计划与前面的执行计划类似,只不过它用于删除操作。

注意 terraform destroy执行的操作相当于你删除了所有配置代码,然后运行terraform apply。

通过在命令行输入yes,确认自己希望应用销毁计划。等待几分钟,让Terraform进行处理,然后你将收到Terraform已经销毁了所有资源的通知。输出将如下所示。

aws_instance.helloworld: Destroying… [id=i-070098fcf77d93c54]
aws_instance.helloworld: Still destroying...
➥[id=i-070098fcf77d93c54, 10s elapsed]  
aws_instance.helloworld: Still destroying...
➥[id=i-070098fcf77d93c54, 20s elapsed]  
aws_instance.helloworld: Still destroying...
➥[id=i-070098fcf77d93c54, 30s elapsed]  
aws_instance.helloworld: Destruction complete after 31s

Destroy complete! Resources: 1 destroyed. 

通过刷新AWS控制台,或者运行terraform show命令并确认它没有返回任何东西,验证资源确实被销毁了。

1.3 新的“Hello Terraform!”

我喜欢经典的“Hello World!”示例,并认为它是一个不错的入门项目,但我不认为它系统地展示了整个技术。Terraform不仅可以从静态配置代码置备资源,还能够基于外部查询和数据查找的结果动态置备资源。我们讨论一下数据源,该元素允许在运行时获取数据并执行计算

本节将改进经典的“Hello World!”示例,添加一个数据源来动态查找Ubuntu AMI的最新值。我们将把输出值传入aws_instance,这样就不必在EC2实例的资源配置中静态设置AMI了(参见   图1.10)。

图1.10 aws_ami数据源的输出如何与aws_instance资源的输入连接到一起

因为我们已经配置了AWS提供程序,并使用terraform init初始化了Terraform,所以可以跳过之前的一些步骤。在这里,我们将执行下面的步骤。

(1)修改Terraform配置来添加数据源。

(2)使用terraform apply进行重新部署。

(3)使用terraform destroy进行清理。

图1.11演示了部署流程。

图1.11 部署流程

1.3.1 修改Terraform配置

我们需要添加从外部数据源读取数据的代码,以便能够查询最新发布到AWS的Ubuntu AMI。编辑main.tf,使其如代码清单1.3所示。

代码清单1.3 main.tf

provider "aws" {
  region = "us-west-2"

data "aws_ami" "ubuntu" {    ⇽---  声明一个名为“ubuntu”的aws_ami数据源
  most_recent = true

  filter {    ⇽---  设置一个过滤器,以选择名称与这个正则表达式匹配的所有AMI
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }

  owners = ["099720109477"]  ⇽--- 规范的Ubuntu AWS账户ID
}
resource "aws_instance" "helloworld" {
  ami           = data.aws_ami.ubuntu.id    ⇽---  将资源链接起来
  instance_type = "t2.micro"
  tags = {
    Name = "HelloWorld"
  }
}

与资源一样,要声明数据源,需要创建一个HCL对象,其类型为“data”,且具有两个标签。第一个标签指定数据源的类型,第二个标签是数据源的名称。类型和名称合起来构成了数据源的标识符,标识符在一个模块内必须保持唯一。图1.12演示了数据源的语法。

图1.12 数据源的语法

数据源代码块的内容称为“查询约束实参”。它们的行为与资源的实参的行为相同。查询约束实参用于指定从哪个(哪些)资源获取数据。数据源是不受管理的资源,Terraform能够从它们读取数据,但不能直接控制它们。

1.3.2 应用修改

接下来,我们应用修改,让Terraform部署一个使用Ubuntu数据源的输出值作为AMI的EC2实例。这需要运行terraform apply。CLI输出如下所示。

$ terraform apply

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:

  # aws_instance.helloworld will be created
  + resource "aws_instance" "helloworld" {
      + ami                          = "ami-0928f4202481dfdf6"    ⇽---  使用数据源的输出进行设置
      + arn                          = (known after apply) 
      + associate_public_ip_address   = (known after apply)
      + availability_zone            = (known after apply)
      + cpu_core_count               = (known after apply) 
      + cpu_threads_per_core          = (known after apply)
      + get_password_data            = false 
      + host_id                      = (known after apply)
      + id                           = (known after apply) 
      + instance_state               = (known after apply)
      + instance_type                = "t2.micro"
     // skip some logs
    }

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来应用修改。等待几分钟后,输出将如下所示。

aws_instance.helloworld: Creating...
aws_instance.helloworld: Still creating... [10s elapsed]
aws_instance.helloworld: Creation complete after 19s [id=i-0c0a6a024bb4ba669]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed. 

与之前一样,可通过访问AWS控制台或者调用terraform show来验证修改。

1.3.3 销毁基础设施

运行terraform destroy,销毁上一步创建的基础设施。注意,这里仍然需要手动确认。

$ terraform destroy
aws_instance.helloworld: Refreshing state... [id=i-0c0a6a024bb4ba669]

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

Terraform will perform the following actions:

  # aws_instance.helloworld will be destroyed
  - resource "aws_instance" "helloworld" {
       - ami                          = "ami-0928f4202481dfdf6" -> null
       - arn                          = "arn:aws:ec2:us-west-2:215974853022
       ➥:instance/i-0c0a6a024bb4ba669" -> null
       - associate_public_ip_address   = true -> null
// skip some logs
    }

Plan: 0 to add, 0 to change, 1 to destroy.
Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

Enter a value:

手动确认并等待几分钟后,EC2实例将成功销毁。

aws_instance.helloworld: Destroying... [id=i-0c0a6a024bb4ba669]
aws_instance.helloworld: Still destroying... 
➥[id=i-0c0a6a024bb4ba669, 10s elapsed]
aws_instance.helloworld: Still destroying...
➥[id=i-0c0a6a024bb4ba669, 20s elapsed]
aws_instance.helloworld: Still destroying...
➥[id=i-0c0a6a024bb4ba669, 30s elapsed]
aws_instance.helloworld: Destruction complete after 30s

Destroy complete! Resources: 1 destroyed. 

1.4 炉边谈话

本章不仅讨论了什么是Terraform,相对于其他IaC工具它具有哪些优缺点,还讲述了如何执行两个实际部署。第一个部署是Terraform的“Hello World!”示例,第二个部署是我个人偏爱的部署,因为它使用数据源演示了Terraform的动态能力。

本文摘自《Terraform 实战》

本书基于实际项目,揭示如何使用Terraform自动扩展和管理基础架构。本书重点介绍了Terraform 0.12的语法、基础知识和高级设计(如零停机时间部署和创建Terraform提供程序)。本书主要内容包括如何使用Terraform,如何管理Terraform资源的生命周期,如何编程,如何在AWS云中部署多层的Web应用程序,如何实现无服务器的部署,如何通过Terraform部署服务器,如何实现零停机部署,如何测试、重构,如何扩展Terraform,如何通过Terraform自动部署,如何实现安全管理。

本书适合作为系统管理员、DevOps工程师、开发人员的自学和参考用书。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐