《Terraform 101 从入门到实践》本手册在南瓜慢话官网和GitHub同步更新。书中的示例代码也放在了GitHub上,供大家参考。介绍完Terraform的一些基本概念之后,我们可以先了解一下Terraform的语法,也就是HCL的语法。变量变量是实现代码重用的一种方式。同一代码的不同变量往往会产生不同的效果。在Terraform中,有一个很重要的概念,就是变量从属于模块。不能跨模块引用变量。即模块A中定义的变量X不能在模块B中直接引用,但是父模块的变量可以作为子模块的入参;子模块的输出变量可以被父模块获取。从语言的角度来看,变量类型与任何编程语言都一样。变量有类型。Terraform的变量类型从语言的角度可以分为两类:基本类型和复合类型,如下:基本类型:string字符串,如“pkslow.com”数字,如319或5.11Booleanbool,如true组合type:listlist(),比如["dev","uat","prod"]setset(),比如set(...)mappingmap(),这样as{name="Larry",age="18"}objectobject({name1=T1,name2=T2})tupletuple([T1,T2,T3...])如果你不想指定一个某种类型,你可以用any来表示任何类型;或者如果你不指定它,它默认为任何类型。从功能上看,变量可以分为输入变量、输出变量和局部变量。输入变量是模块接收外部变量的方式。它在变量块中定义,如下所示:list(object({internal=numberexternal=numberprotocol=string}))default=[{internal=8300external=8300protocol="tcp"}]}输出变量定义了一个模块external返回的变量由输出定义块,如下:output"instance_ip_addr"{value=aws_instance.server.private_ip}局部变量是模块中定义的临时变量,可以引用,定义在locals块中,如下:locals{service_name="forum"owner="CommunityTeam"}InputVariableInputVariable输入变量定义在变量块中,它就像函数的输入参数。定义输入变量定义变量有很多可选属性:type类型:指定变量是什么类型;如果没有指定,它可以是任何类型;默认值default:变量的默认值,定义后不需要提供变量的值,注意值的类型要对应类型;description:解释这个变量的作用和使用;校验validation:提供校验逻辑判断输入变量是否合法;sensitivitysensitive:定义变量是否敏感,如果是,则不会显示;默认为假;nullablenullable:如果为true,则可以为空,否则不能。默认为真。所有属性都明确指定,如以下示例所示:","prod"],var.env)error_message="环境必须是dev/uat/prod之一。"}}变量名为env,表示环境名。默认值是dev,值必须是dev,uat和prod之一。如果输出无效值,会报错:$terraformplan-var="env=sit"?│Error:Invalidvalueforvariable││oninput.tfline1:│1:variable"env"{││env必须是dev/uat/prod之一。输入变量的使用只有定义了变量才能使用,使用方式为var.name。比如这里定义了两个变量env和random_string_length:variable"env"{type=stringdefault="dev"}variable"random_string_length"{type=numberdefault=10}用法如下:resource"random_string""random"{length=var.random_string_lengthlower=truespecial=false}locals{instance_name="${var.env}-${random_string.random.result}"}output"instance_name"{value=local.instance_name}将变量传递给rootmodule从外部向根模块导入变量的方法有很多种。常见的如下,优先级从低到高:环境变量exportTF_VAR_image_id=ami-abc123terraform.tfvars文件;terraform.tfvars.json文件;*.auto.tfvars或*.auto.tfvars.json文件;命令行参数-var传入一个变量;命令行参数-var-file传入一个变量集合文件;实际中,最常用的是通过命令行来传入参数,因为一般需要在不同的环境中指定具体的变量,所以会把变量放在文件中,然后具体环境的主文件通过命令行指定:$terraformapply-var="env=uat"$terraformapply-var-file="prod.tfvars"prod.tfvars内容如下:env="prod"random_string_length=12我们可以定义dev.tfvars、uat.tfvars和prod.tfvars等,使用不同的对于环境变量,直接改文件名即可输出变量。输出变量有输入和输出。输出变量就像模块的返回值。比如我们调用一个模块创建一个服务,那么我们就需要获取该服务的IP。这个IP是事先不知道的,是创建服务器后的结果之一。输出变量有以下作用:子模块的输出变量可以暴露一些资源属性;根模块的输出变量apply后可以输出到控制台;根模块的输出变量可以通过远程状态与其他Terraform配置共享,作为数据源。定义输出变量输出变量需要在输出块中定义,如下所示:只有执行apply时才会计算输出变量,likeplan不会执行计算。还可以定义输出变量的一些属性:description:输出变量的描述,清楚的说明这个变量是做什么用的;sensitive:如果为true,则不会在控制台打印;depends_on:显式定义依赖关系。完整定义如下:output"instance_ip_addr"{value=aws_instance.server.private_ipdescription="主服务器实例的私有IP地址。"sensitive=falsedepends_on=[#必须先创建安全组规则才能#实际使用此IP地址,否则服务将无法访问。aws_security_group_rule.local_access,]}referenceoutputvariable引用输出变量很简单,表达式为module..,如果之前的输出变量定义在模块pkslow_server中,则引用为:module.pkslow_server。instance_ip_addr。局部变量LocalVariable局部变量有点类似于其他语言代码中的局部变量。在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.InputVariablesInputVariablesvar.LocalVariablesLocalValueslocal.子模块module..DataSourcesdata.的输出。path和Terraform相关的path.module:模块的路径path.root:根模块的路径path.cwd:一般和根模块一样,除了其他高级用法terraform.workspace:在工作区名称块局部变量count.index:计数循环的下标;each.key/each.value:foreach循环的键值;self:提供者中的引用;以上都是单值引用,如果是复杂的列表或者Map类型,需要用方括号[]来引用。aws_instance.example[0].id:指其中一个元素;aws_instance.example[*].id:指列表的所有id值;aws_instance.example["a"].id:指key为a的元素;[forvalueinaws_instance.example:value.id]:返回所有ids作为列表;运算符和其他语言一样,Terraform也有可以使用的运算符,主要用于数值计算和逻辑计算。以下运算符按优先级从高到低依次为:!Negative、-Negative*Multiply、/Divide、%Module+Plus、-Minus>、>=、<、<=:比较符号==等于、!=Not等于&&与门||或门当然,这些优秀的层次可以用括号来改变,比如(1+2)*3。注意:对于结构化数据比较,需要注意类型是否一致。例如,var.list==[]应该在逻辑上返回true,但是当列表为空时。当[]实际上代表一个元组([]),所以它们不匹配。可以使用length(var.list)==0。条件表达式条件表达式的作用是在两个值中选择一个,如果条件为真则选择第一个,如果条件为真则选择第二个错误的。形式如下:条件?true_value:false_value例子如下:env=var.env!=""?var.env:"dev"表示给env赋值,如果var.env不为空,就输入变量var.env的值赋值,如果为空,赋默认值dev。for表达式使用for表达式创建一些复杂的值,可以使用一些转换和计算来计算和返回值。例如将字符串列表转为大写:>[forsin["larry","Nanhua","Deng"]:upper(s)]["LARRY","NANHUA","DENG",]即可比例和取值如下:>[fori,vin["larry","Nanhua","Deng"]:"${i}.${v}"]["0.larry","1.Nanhua","2.Deng",]ForexpressionofMap:>[fork,vin{name:"LarryDeng",age:18,webSite:"www.pkslow.com"}:"${k}:${v}"]["age:18","name:LarryDeng","webSite:www.pkslow.com",]按条件过滤数据:>[foriinrange(1,10):i*3ifi%2==0][6,12,18,24,]DynamicBlockDynamicBlock的作用是根据变量重复某个block的配置。这在Terraform中得到满足。资源"aws_elastic_beanstalk_environment""tfenvtest"{name="tf-test-name"application="${aws_elastic_beanstalk_application.tftest.name}"solution_stack_name="64bitAmazonLinux2018.03v2.11.4runningGo1.12.6"动态“设置”{for_each=var.settingscontent{namespace=setting.value["namespace"]name=setting.value["name"]value=setting.value["value"]}}}就像这里的例子一样,设置块将是重复。重复次数取决于for_each后面的变量。