美文网首页
《Terraform 101 从入门到实践》 第五章 HCL语法

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

作者: 南瓜慢说 | 来源:发表于2023-02-10 10:44 被阅读0次

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


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

    变量Variables

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

    变量类型

    从语言角度

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

    基本类型:

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

    组合类型:

    • 列表list(<T>),如["dev", "uat", "prod"]
    • 集合set(<T>),如set(...)
    • 映射map(<T>),如{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:模块所在路径<br />path.root:根模块的路径<br />path.cwd:一般与根模块相同,其它高级用法除外<br />terraform.workspace:工作区名字
    块中的本地变量 count.index:count循环的下标;<br />each.key/each.value:for each循环的键值;<br />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语法

          本文链接:https://www.haomeiwen.com/subject/wbrkkdtx.html