草庐IT

《Terraform 101 从入门到实践》 第五章 HCL语法

南瓜慢说 www.pkslow.com 2023-04-17 原文

《Terraform 101 从入门到实践》这本小册在南瓜慢说官方网站GitHub两个地方同步更新,书中的示例代码也是放在GitHub上,方便大家参考查看。


介绍了Terraform一些比较基础的概念后,我们可以先了解一下Terraform的语法,也就是HCL的语法。

变量Variables

变量是实现代码复用的一种方式,同样的代码不同的变量往往会有不同的效果。而在Terraform里,有一个概念非常重要,就是变量都是从属于模块的。变量无法跨模块引用。即在模块A定义的变量X,无法在模块B中直接引用。但父模块的变量,可以作为子模块的入参;而子模块的输出变量可以被父模块获取。

变量类型

从语言角度

跟任何编程语言一样,变量都是有类型的,Terraform的变量类型从语言的角度可分为两大类:基本类型和组合类型,具体如下:

基本类型:

  • 字符串string,如"pkslow.com"
  • 数字number,如3195.11
  • 布尔值bool,如true

组合类型:

  • 列表list(),如["dev", "uat", "prod"]
  • 集合set(),如set(...)
  • 映射map(),如{name="Larry", age="18"}
  • 对象object({name1=T1, name2=T2})
  • 元组tuple([T1,T2,T3...])

如果不想指定某个类型,可以用any来表示任意类型;或者不指定,默认为任意类型。

从功能角度

从功能角度来看,变量可以分为输入变量、输出变量和本地变量。

输入变量是模块接收外部变量的方式,它定义在variable块中,如下:

variable "image_id" {
  type = string
}

variable "availability_zone_names" {
  type    = list(string)
  default = ["us-west-1a"]
}

variable "docker_ports" {
  type = list(object({
    internal = number
    external = number
    protocol = string
  }))
  default = [
    {
      internal = 8300
      external = 8300
      protocol = "tcp"
    }
  ]
}

输出变量定义了一个模块对外返回的变量,通过output块来定义,如下:

output "instance_ip_addr" {
  value = aws_instance.server.private_ip
}

本地变量是模块内定义且可引用的临时变量,在locals块中定义,如下:

locals {
  service_name = "forum"
  owner        = "Community Team"
}

输入变量Input Variable

输入变量是定义在variable块中的,它就像是函数的入参。

定义输入变量

定义variable有很多可选属性:

  • 类型type:指定变量是什么类型;如果没有指定,则可以是任意类型;
  • 默认值default:变量的默认值,定义后可以不用提供变量的值,注意它的值的类型要与type对应上;
  • 说明description:说明这个变量的作用和用途;
  • 校验validation:提供校验逻辑来判断输入的变量是否合法;
  • 敏感性sensitive:定义变量是否敏感,如果是则不会显示;默认为false
  • 可空nullable:如果为true则可以为空,否则不能。默认为true

所有属性都显性指定如下面例子所示:

variable "env" {
  type        = string
  default     = "dev"
  description = "environment name"
  sensitive   = false
  nullable    = false
  validation {
    condition     = contains(["dev", "uat", "prod"], var.env)
    error_message = "The env must be one of dev/uat/prod."
  }
}

这个变量名为env,表示环境名,默认值为dev,这个值必须为devuatprod中的其中一个。如果输出一个非法的值,会报错:

$ terraform plan -var="env=sit"
╷
│ Error: Invalid value for variable
│ 
│   on input.tf line 1:
│    1: variable "env" {
│ 
│ The env must be one of dev/uat/prod.

使用输入变量

只有定义了变量才可以使用,使用的方式是var.name。比如这里定义了两个变量envrandom_string_length

variable "env" {
  type        = string
  default     = "dev"
}

variable "random_string_length" {
  type    = number
  default = 10
}

则使用如下:

resource "random_string" "random" {
  length  = var.random_string_length
  lower   = true
  special = false
}

locals {
  instance_name = "${var.env}-${random_string.random.result}"
}

output "instance_name" {
  value = local.instance_name
}

传入变量到根模块

要从外部传入变量到根模块,有多种方式,常见的有以下几种,按优先级从低到高:

  • 环境变量export TF_VAR_image_id=ami-abc123

  • terraform.tfvars文件;

  • terraform.tfvars.json文件;

  • *.auto.tfvars*.auto.tfvars.json文件;

  • 命令行参数-var传入一个变量;命令行参数-var-file传入一个变量的集合文件;

在实践中,最常用的还是通过命令行来传入参数,因为一般需要指定不同环境的特定变量,所以会把变量放到文件中,然后通过命令行指定特定环境的主文件:

$ terraform apply -var="env=uat"
$ terraform apply -var-file="prod.tfvars"

prod.tfvars的内容如下:

env                  = "prod"
random_string_length = 12

我们可以定义dev.tfvarsuat.tfvarsprod.tfvars等,要使用不同环境的变量就直接改变文件名即可。

输出变量Output Variable

有输入就有输出,输出变量就像是模块的返回值,比如我们调用一个模块去创建一台服务,那就要获取服务的IP,这个IP事先是不知道,它是服务器创建完后的结果之一。输出变量有以下作用:

  • 子模块的输出变量可以暴露一些资源的属性;
  • 根模块的输出变量可以在apply后输出到控制台;
  • 根模块的输出变量可以通过remote state的方式共享给其它Terraform配置,作为数据源。

定义输出变量

输出变量需要定义在output块中,如下:

output "instance_ip_addr" {
  value = aws_instance.server.private_ip
}

这个value可以是reource的属性,也可以是各种变量计算后的结果。只要在执行apply的时候才会去计算输出变量,像plan是不会执行计算的。

还可以定义输出变量的一些属性:

  • description:输出变量的描述,说明清楚这个变量是干嘛的;
  • sensitive:如果是true,就不会在控制台打印出来;
  • depends_on:显性地定义依赖关系。

完整的定义如下:

output "instance_ip_addr" {
  value       = aws_instance.server.private_ip
  description = "The private IP address of the main server instance."
  sensitive   = false
  depends_on = [
    # Security group rule must be created before this IP address could
    # actually be used, otherwise the services will be unreachable.
    aws_security_group_rule.local_access,
  ]
}

引用输出变量

引用输出变量很容易,表达式为module.<module name>.<output name>,如果前面的输出变量定义在模块pkslow_server中,则引用为:module.pkslow_server.instance_ip_addr

本地变量Local Variable

本地变量有点类似于其它语言代码中的局部变量,在Terraform模块中,它的一个重要作用是避免重复计算一个值。

locals {
  instance_name = "${var.env}-${random_string.random.result}-${var.suffix}"
}

这里定义了一个本地变量instance_name,它的值是一个复杂的表达式。这时我们可以通过local.xxx的形式引用,而不用再写复杂的表达式了。如下:

output "instance_name" {
  value = local.instance_name
}

这里要特别注意:定义本地变量的关键字是locals块,里面可以有多个变量;而引用的关键字是local,并没有s

一般我们是建议需要重复引用的复杂的表达式才使用本地变量,不然太多本地变量就会影响可读性。

对变量的引用

定义了变量就需要对其进行引用,前面的讲解其实已经讲过了部分变量的引用,这些把所有列出来。

类型 引用方式
资源Resources <Resource Type>.<Name>
输入变量Input Variables var.<NAME>
本地变量Local Values local.<NAME>
子模块的输出 module.<Module Name>.<output Name>
数据源Data Sources data.<Data Type>.<Name>
路径和Terraform相关 path.module:模块所在路径
path.root:根模块的路径
path.cwd:一般与根模块相同,其它高级用法除外
terraform.workspace:工作区名字
块中的本地变量 count.index:count循环的下标;
each.key/each.value:for each循环的键值;
self:在provisioner的引用;

上面都是单值的引用,如果是List或Map这种复杂类型,就要使用中括号[]来引用。

aws_instance.example[0].id:引用其中一个元素;

aws_instance.example[*].id:引用列表的所有id值;

aws_instance.example["a"].id:引用key为a的元素;

[for value in aws_instance.example: value.id]:返回所有id为列表;

运算符

与其它语言一样,Terraform也有运算符可以用,主要是用于数值计算和逻辑计算。以下运算符按优先级从高到低如下:

  1. !取反,-取负
  2. *乘号,/除号,%取余
  3. +加号,-减号
  4. >>=<<=:比较符号
  5. ==等于,!=不等于
  6. &&与门
  7. ||或门

当然,用小括号可以改变这些优秀级,如(1 + 2) * 3

注意:对于结构化的数据比较需要注意类型是否一致。比如var.list == []按理说应该返回true,而list为空时。当[]实际表示是元组tuple([]),所以它们不匹配。可以使用length(var.list) == 0的方式。

条件表达式

条件表达式的作用是在两个值之间选一个,条件为真则选第一个,条件为假则选第二个。形式如下:

condition ? true_value : false_value

示例如下:

env = var.env !="" ? var.env : "dev"

意思是给env赋值,如果var.env不为空就把输入变量var.env的值赋给它,如果为空则赋默认值dev

for表达式

使用for表达式可以创建一些复杂的值,而且可以使用一些转换和计算对值计算再返回。如将字符串列表转化成大写:

> [for s in ["larry", "Nanhua", "Deng"] : upper(s)]
[
  "LARRY",
  "NANHUA",
  "DENG",
]

可以获取下标和值:

> [for i,v in ["larry", "Nanhua", "Deng"] : "${i}.${v}"]
[
  "0.larry",
  "1.Nanhua",
  "2.Deng",
]

对于Map的for表达式:

> [for k,v in {name: "Larry Deng", age: 18, webSite: "www.pkslow.com"} : "${k}: ${v}"]
[
  "age: 18",
  "name: Larry Deng",
  "webSite: www.pkslow.com",
]

通过条件过滤数据:

> [for i in range(1, 10) : i*3 if i%2==0]
[
  6,
  12,
  18,
  24,
]

动态块Dynamic Block

动态块的作用是根据变量重复某一块配置。这在Terraform是会遇见的。

resource "aws_elastic_beanstalk_environment" "tfenvtest" {
  name                = "tf-test-name"
  application         = "${aws_elastic_beanstalk_application.tftest.name}"
  solution_stack_name = "64bit Amazon Linux 2018.03 v2.11.4 running Go 1.12.6"

  dynamic "setting" {
    for_each = var.settings
    content {
      namespace = setting.value["namespace"]
      name = setting.value["name"]
      value = setting.value["value"]
    }
  }
}

比如这里的例子,就会重复setting块。重复的次数取决于for_each后面跟的变量。

有关《Terraform 101 从入门到实践》 第五章 HCL语法的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - 树顶语法无限循环 - 2

    我脑子里浮现出一些关于一种新编程语言的想法,所以我想我会尝试实现它。一位friend建议我尝试使用Treetop(Rubygem)来创建一个解析器。Treetop的文档很少,我以前从未做过这种事情。我的解析器表现得好像有一个无限循环,但没有堆栈跟踪;事实证明很难追踪到。有人可以指出入门级解析/AST指南的方向吗?我真的需要一些列出规则、常见用法等的东西来使用像Treetop这样的工具。我的语法分析器在GitHub上,以防有人希望帮助我改进它。class{initialize=lambda(name){receiver.name=name}greet=lambda{IO.puts("He

  3. ruby-on-rails - 使用 Sublime Text 3 突出显示 HTML 背景语法中的 ERB? - 2

    所以我在关注Railscast,我注意到在html.erb文件中,ruby代码有一个微弱的背景高亮效果,以区别于其他代码HTML文档。我知道Ryan使用TextMate。我正在使用SublimeText3。我怎样才能达到同样的效果?谢谢! 最佳答案 为SublimeText安装ERB包。假设您安装了SublimeText包管理器*,只需点击cmd+shift+P即可获得命令菜单,然后键入installpackage并选择PackageControl:InstallPackage获取包管理器菜单。在该菜单中,键入ERB并在看到包时选择

  4. ruby - 覆盖相似的方法,更短的语法 - 2

    在Ruby类中,我重写了三个方法,并且在每个方法中,我基本上做同样的事情:classExampleClassdefconfirmation_required?is_allowed&&superenddefpostpone_email_change?is_allowed&&superenddefreconfirmation_required?is_allowed&&superendend有更简洁的语法吗?如何缩短代码? 最佳答案 如何使用别名?classExampleClassdefconfirmation_required?is_a

  5. ruby 语法糖 : dealing with nils - 2

    可能已经问过了,但我找不到它。这里有2个常见的情况(对我来说,在编程Rails时......)用ruby​​编写是令人沮丧的:"astring".match(/abc(.+)abc/)[1]在这种情况下,我得到一个错误,因为字符串不匹配,因此在nil上调用[]运算符。我想找到的是比以下内容更好的替代方法:temp="astring".match(/abc(.+)abc/);temp.nil??nil:temp[1]简而言之,如果不匹配,则简单地返回nil而不会出错第二种情况是这样的:var=something.very.long.and.tedious.to.writevar=some

  6. ruby - Ruby 语法糖有 "rules"吗? - 2

    我正在学习Ruby的基础知识(刚刚开始),我遇到了Hash.[]method.它被引入a=["foo",1,"bar",2]=>["foo",1,"bar",2]Hash[*a]=>{"foo"=>1,"bar"=>2}稍加思索,我发现Hash[*a]等同于Hash.[](*a)或Hash.[]*一个。我的问题是为什么会这样。是什么让您将*a放在方括号内,是否有某种规则可以在何时何地使用“it”?编辑:我的措辞似乎造成了一些困惑。我不是在问数组扩展。我明白了。我的问题基本上是:如果[]是方法名称,为什么可以将参数放在括号内?这看起来几乎——但不完全是——就像说如果你有一个方法Foo.d

  7. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  8. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  9. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  10. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

随机推荐