Terraform
Terraform
建议大家可以阅读一下这篇中文terraform文档
英文好的读下terraform up and running
why为什么使用terraform,和如何选择iac tools
这里书中通过几项对比
Configuration management && Provisioning
配置管理还是编排工具呢
- Puppet && Ansible && SaltStack
- CloudFormation, Terraform, OpenStack Heat
如果不适用镜像工具等,同时又需要同步配置,建议同时使用
Mutable Infrastrucure && Immutable Infrastructure
可变的,不一样的 && 一样的,不变的
通常类似puppet管理的服务器,越来越多,同时非puppet模块内容的,
每个服务器都可能长得不一样,甚至每个版本的东西都可能执行的不一样,
而terraform通过packer打出来的服务器,一模一样,但是一模一样的代价
是需要部署时间,并且服务会有重启的操作。就好像重启服务,一个运行
systemctl restart xxx-service,一个把机器换个硬盘重启了
Procedural Language && Declarative Language
流程化语言还是声明式语言
ansible
- ec2: count: 10 image: ami-xxx instance_type: t2.micro
resource "aws_instance" "example" { count = 10 ami = "ami-xxx" instance_type = "t2.micro" }
当我们尝试把数字更新到15的时候,ansible会创建另外15个服务器,而terraform则会
追加到15台服务器, terraform是有状态的,而ansible只做它应该做的事情
事务性的流程化语言按照流程处理就会结束,一次只做一件事情,类似api的功能
而声明式的则会找到上次的状态并进行再次构建,更加方便重用,类似服务的功能
有个缺点就是使用的功能局限在云厂商对于terraform的支持上,如果没有,则需要自己实现
而这通常会比较复杂
Master && Masterless
有管控机master,没有管控机master
管控机的好处:
- 配置在一个地方,只需要管理管控机就好了
- 有统一的配置接口,方便调用
- 一般是后台运行,比如puppet agent可能每半个小时请求下master,然后同步配置
管控机的坏处:
- 额外的基础设施
- 安全性
- 额外的维护工作,升级部署等
无管控机的好处:
- 不用管理管控机
- 通过一些通用组件来连接,比如ssh或者云的各种connections等
Large Community && Small Community
一般选择技术方案,也会看这个技术的社区大小。
社区决定了解决问题时间和实现的质量等各种问题。其实我们了解到的这些,社区都已经足够。
但是我们可以从几个角度看社区的大小
- IsOpen
是否开源
- Contributors, Stars,上次commit的时间
有多少人贡献代码,有多少颗红心,这个可以看github
- StackOverflow有多少相关问题
- Jobs
相关工作jd里面多少包含
Mature && Cutting Edge
成熟的,久远的 还是 持续更新的
IaC(Infrastructure as Code)代码即基础设施
为什么使用terraform?为什么使用Iac?
下面部分来自terraform up and running
self-service
自动化创建一整个服务,而不会需要了解很多奇怪的东西
speed and safety
更快更安全
Documentation
代码就是文档,通过文档可以读取对应的结构等等信息
Version control & Validation
版本控制,可以记录整个变更历史,方便回滚、测试等操作
Reuse
方便重用,主要在多云、多环境等方面
Happiness
幸福感,正常管理云资源等是比较枯燥和繁琐的,而有了IaC,我们需要做的就是不断升级我们的
版本。
terraform具体使用
精简版可以参考我这两篇文档terraform使用和terraform使用和发布
自己本地试试,没有云账户,用docker也可以的, 我的例子以腾讯云为例,其他差不多
编辑器
使用前,建议配置下编辑器,
一般的编辑器都支持HCL格式的文件,这个时候去搜索下,emacs下安装terraform-mode即可
provider
provider相当于服务商,国内比如阿里云,腾讯云,华为云,国外AWS,公共的,比如docker,openstack 等等。
所有支持的providers列表, 基本上都覆盖了。
https://registry.terraform.io/browse/providers
定义一个provider很简单
|
|
packer打镜像工具
packer是使用terraform很重要的一步
建议先阅读下packer started和packer官方文档
如果没时间可以参考下我的流程
编写hcl文件
|
|
上面这个会创建一个临时服务器,并且对这个服务器执行ansible-playbook操作
进行额外装配,操作完成以后就会自动打镜像,失败临时packer服务器就会销毁。
实际操作命令, 具体操作可以packer相关,需要注意一个问题,调用packer
需要用=绝对路径=,因为默认云服务器可能也有一个packer,那个是db相关的工具,为了避免麻烦
请按照绝对路径操作
|
|
resource
provider好像是告诉我们要去超市还是去批发市场,resource则是具体的买了,
针对resource,一个典型就是云服务器,云网络,这些都抽象为Resource, 也就是云厂商
卖的具体的东西,一直具体到一个EIP等等
比如aws文中的
|
|
这个在provider列表中,对应链接里面有对应的厂商的privder使用文档
init && plan && apply
一般terraform工作流分为init–>plan–>apply–>destroy
具体请参考官方文档terraform流程
init
比如我们cd到工程目录或者我们copy一个terraform目录,这个时候需要init
init干嘛,通常会帮我们生成几个文件,如果是新项目
如果是老项目,则会安装对应的provider插件,相当于工程准备工作
init以后注意 .terraform .tfstate .tfstate.backup不要带上版本管理
你可以把需要的写进各个tf文件里面
plan
plan是计划,如果我们改了配置文件或者需要操作什么,会生成一个计划,
terraform plan > liuliancao.plan
plan会告诉我们,比如这次会怎么样,销毁几台机器还是创建几台机器,用户确认后,
可以继续后面的操作
apply
apply是应用,可能是一个plan,也可能是一个工程直接apply
一般terraform apply后,会提示确认,比如创建什么,销毁什么
destroy
当我们的资源infrasture不需要的时候,则可以进行销毁,销毁是危险操作,需要确认
工作目录等信息。
provisions
provisions具体建议看下官网provision相关 这个一般是通过connections解决,但是我一般不建议插入太多的connection
建议用同步工具比如puppet, ansible等操作
因为如果创建服务器是循环的,则可能会导致卡住, 我来说下可能用到的一些场景
remote exec
remote表示对刚刚创建的服务器使用一些命令
local exec
local就是本地操作, 这里可能会有个问题就是,我local exec ssh 这台服务器执行某个命令
或者依赖它up才会执行
这里就需要这样写一下
|
|
- 第一个我们可以使用terraform self使用instance的相关信息
- 第二个我们需要等ssh通才能执行
这也是我为啥不建议在这里写
那怎么来呢,把terraform当成资源购买的方式,如果想这个资源都有啥,尽量在镜像里面
也就是packer里面通过这种方式打好,而其他的不同的尽量收敛到注册中心等地方
如果实在需要,这段时间无法改造,我有两个办法
- 用这种local-exec的方式,调用单个ansible执行
- 开机任务(linux放到rc.local,windows放到计划任务)
variables
为什么说terraform是一门语言,因为它可以定义变量定义数据结构,条件等等
是hashcorp家的hcl语言方式,consul也是他们公司的,
变量可以说是非常重要的,定义好变量才是整个工作流的开始,建议阅读官方terraform变量教程
|
|
定义一个变量,可以定义报错信息和正则信息
如果需要保护这个变量不在apply或者plan日志里面打印,比如这个变量设置了默认值,key或者密码
则追加sensitive = true
其他地方如何引入 ${var.security_group_id}这样即可使用
尽量在资源等有必要的地方使用变量,然后variables.tf定义默认变量或者不定义
这样会提高安全性,可定制性,也更方便后面迁移到模块
关于变量需要知道
- variables.tf是定义变量的地方,建议需要动态指定的配置,就是我们执行命令的选项类似, 请放到变量里面,相当于ansible的roles/defaults.yml roles/vars.yml
- terraform.tfvars这种变量,表示我们执行terraform plan or apply的时候指定的变量
可以和ansible对比理解,tfvars是放到playbooks或者vars.yml里面的
data
data是数据,这个数据是资源携带的,定义data是为了方便后续使用
一个典型场景,比如,我想获取所有满足某个条件的镜像,那么我们的main.tf可能这样写
|
|
output
具体请查阅terraform outputs官网 outputs.tf的作用其实就是定义很多个data,方便各个tf使用,和模块导出给其他使用
template
模板可以定义把服务器转化成ansible的格式
|
|
state
关于state可以看terraform state官方文档
可以看下apply以后维护的.terraform.tfstate这个文件,其实里面包含了很多json信息
state可以放到云provider上面,防止丢失
默认不能同时执行terraform两次,因为会加锁,也不建议terraform的时候Ctrl-C,
这是相当危险的操作,可能会造成不一致,但是这个时候可以继续执行通常都能恢复,
最怕的是Provider Bug.
文中介绍了一种目录结构,具体可以看书116页哈
|
|
这里介绍了定义terraform remote state定义的方法,
首先上层模块定义output用于想依赖模块准备输出,比如下游另一个web服务需要这个db的
端口地址等信息,则可以通过调用对应的state知道
|
|
获取远程state
|
|
下游准备使用
这个时候在resource里面的user_data == <<EOF #!/bin/bash echo "Hello, World!" echo "${data.terraform_remote_state.db.address}" >> index.html EOF 或者其他各个地方都可以使用这个了
这里还要注意terraform state有几个命令比较方便
- terraform taint
taint是污染的意思,当我们对一个资源执行taint的时候,那么这个资源会被强制删除
并且创建一个新的
- terraform state list
列出所有的资源,方便后面的操作
- terraform state rm
当我们想把一个资源从terraform挣脱出去的时候,用state rm
这个时候,会新创建一个服务器,并且执行删除等操作的时候都不会再影响它
module
terraform里面的module类似咱们代码里面的函数,工厂等等。
模块定义
和我们直接写一样的,
workspace/ my-module/ vars.tf outputs.tf main.tf user-data.sh
引用的时候
module "my-module" { source = "workspace/my-module"
var1 = "xxx" var2 = "xxx" }
这里文中的例子是
比如A业务需要web服务器,B业务也需要web服务器,只是数量不一样,那么则非常可以使用模块
模块是函数,那么参数就是input.tf里面的var1,var2这些变量,这些是标记不同点
这里有一个技巧就是对于使用模块的一方,可以把自己相关的state文件告知模块
模块放一个变量叫state_path,模块里面就能调用调用方的不同信息,这个是动态的
具体看P139
后面文章也提到了,一个建议就是不要共用vpc,最好一个环境一个vpc或者一个workspace一个vpc
loops
count
foreach
if
terraform支持条件表达式,表达式等都可以使用terraform的常见terraform函数
${var.xxx} == 0 ? 1 : 0
高阶if
配合count来实现if else count = ${var.xxx} == xxx ? 1 : 0
meta create_before_destory
说一个本地安装云provider的办法
正常情况下,都会github timeout
我们可以提前下载好,然后到workspace, 这里以腾讯云为例
|
|
增加terraform apply的时候并发
terraform apply -parallelism=10 这个是并发调整到10,一般可以到100这样
terraform apply的时候不要确认
terraform apply -auto-approve 注意该操作存在危险请确认后再加上
写for_each报错
A reference to "each.value" has been used in a context in which it unavailable, such as when the configuration no longer contains the value in its "for_each" expression. Remove │ this reference to each.value in your configuration to work around this error
这里意思就是说你的遍历有问题
如果用了local
注意是for_each = local.xxx 不是locals
但是定义local的时候要这样写
locals = { xxx = "" }
还要注意是for_each不是foreach,别写错了
terraform条件判断报错
The given key does not identify an element in this collection value
排查下发现是到另一个条件了,terraform console进行条件判断或者打印变量
很好用!
编写terraform测试(216)
测试类型
Unit tests单元测试
单元测试对每一段小代码执行,或者小函数
Integration tests集成测试
包含几个模块之间进行测试
Smoke tests冒烟测试
每次发布的时候执行的测试
测试流程
- 这里文中建议的是region,state远程等变成变量
- 通过外步脚本,创建的时候传入region等参数
- terraform apply && terraform output检查参数
- 整个流程用熟悉的语言编写出来就好了
编写terraform注释(219)
module注释
每一个module编写一个Readme
最好还有一个更详细的说明文档
代码注释
描述代码里面没有的东西,可能被忽略的东西,而不是写一些无用的注释
在variable.tf里面使用description描述变量的使用
示例代码
请为模块编写示例代码,这些代码可以让用户更快地了解这个模块
可以放在README,这些通常也可以放到自动化测试里面
terraform workflow工作流
plan计划
terraform plan -out=liuliancao.plan terraform apply -out=liuliancao.plan
staging进入代码
至少维护两个环境,Production和Staging
code review代码审查
production部署到生产环境
多环境适配
通过tfvars控制每个环境的不同
以liuliancao-blog为例子,version0.1需要发布到version0.2
本地调试,在liuliancao目录下
修改对应的tfvars
tfvars为source = "git::git@github.com:liuliancao/liuliancao-blog.git/blog?ref=0.1"
为
tfvars为source = "git::git@github.com:liuliancao/liuliancao-blog.git/blog?ref=0.2"
terraform init && terraform apply -var-file liuliancao.tfvars
测试,跑单元测试,跑集成测试,通过
terraform destroy
test分支联系QA测试
git上调整测试环境
调整测试环境的tfvars
tfvars为source = "git::git@github.com:liuliancao/liuliancao-blog.git/blog?ref=0.1"
为
tfvars为source = "git::git@github.com:liuliancao/liuliancao-blog.git/blog?ref=0.2"
terraform init && terraform apply -var-file test.tfvars测试指定环境指定版本
没问题terraform destory
调整到线上,git修改tfvars,并且多人review,最终完成线上变更
整体如图(来源terraform-up-and-running) [[ ../images/terraform-up-and-running01.png ]]
terraform把一台机器保留其他销毁
terraform state list terraform state rm xxx.xxx.xx[\"xxx\"] 注意反斜杠,否则会报错 terraform state rm module.xxx.tencentcloud_instance.liuliancao["ap-shanghai-5-2"] ╷ │ Error: Index value required │ │ on line 1: │ (source code not available) │ │ Index brackets must contain either a literal number or a literal string.
terraform发现引用module导致创建多个resource duplicate resource
原因可能是
- 你引入了多次,并且可能是不一样的
- 你module里面名字写重复了
我最近遇到vpc会出现多个的情况,就是vpc的name写成一样的了,这样会导致很奇怪的问题
xxx is a object, known only after apply
写错了资源名称了,一般建议写个depends_on = [ module.xxx ]
terraform remote module from git(terraform使用远程git目录当自己的source)
|
|
- 注意格式是这样的git::git@xxxx.com也可以git::ssh//git@xxx.com
- .git后面是//,/会找不到
- 支持/x/y路径,但是terraform会把整个git目录都拉到.terraform/modules下,可能是为了了解git信息
如果你觉得不好,那就每个模块都写一个git目录,但是这样说实话管理起来比较麻烦
还有一种解决办法是写一个module git专门同步,然后其他的用相对目录引入
remote backend
官方文档有,这里不说了
cos
也是支持的
etcdv3
注意这个报错 {"level":"warn","ts":"2021-12-14T18:10:06.791+0800","caller":"clientv3/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"endpoint://client-d1cc2ec1-b919-405b-b6b1-da553a1b7e64/xxx:2379","attempt":0,"error":"rpc error: code = NotFound desc = etcdserver: requested lease not found"} Error loading state: Failed to lock state in etcd: etcdserver: requested lease not found.
原因是刚开始没有东西可以lock
解决办法:修改backend里面的lock=true为lock=false创建完成以后,再改回来
provisoner command
|
|
通过条件表达式解决
|
|
用模板的时候报错
20: template = file("./version-hosts.tpl") │ │ Invalid value for "path" parameter: no file exists at ./version-hosts.tpl; this function works only with files that are distributed as part of the │ configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of │ that resource. ╵ 最好用../而不是./
建议汇总
- 使用一个称手的编辑器写terraform,并且加上版本控制
- 使用terraform fmt格式化代码或者编辑器内嵌
- 慎用-auto-approve
- 不要用一个vpc,尝试一个环境对应一个,并且有对应的测试vpc等信息, 总之维护一套 测试环境
- count有限制,最大是1024,如果是做一些均衡策略模块,通常需要限制这个到1024
- 给你写的模块通过git tag加上一个版本
- 通过tfvars维护一些default变量而不是永远写variables default, 这样的好处是 类似不同环境配置文件一样,最大限度不破坏代码,而我们需要配置下钩子或者pipeline或者 部署脚本嵌入
- 一旦使用了terraform,尽量所有资源通过terraform管理,如果有其他的,使用terraform import导入
- 永远使用plan来探测可能的错误,特别注意destory的部分
- 使用create_before_destory来控制资源的创建, 使用prevent_destory控制需要保护的资源
- 资源名称等的名字变化不是小问题,可能会带来资源重建,需要谨慎,尽量使用create_before_destory
- 对已有资源如果希望进行管理可以通过terraform import方式进行导入(具体 怎么import一般在proivder对应的resource最后一行都有), import完成以后 可以进行管理或者terraform state show 具体resource 生成tf文件编写
参考文档
- terraform variables
- 挺好的介绍terraform的文档
- terraform up and running
- 官方文档terraform流程
- 官网provision相关
- terraform self
- terraform language
- terraform outputs官网
- terraform state官方文档
- terraform函数
- terraform foreach文档
- terraform count文档
- 一篇不错的terraform文章
- https://cloud.google.com/docs/terraform/get-started-with-terraform
- https://github.com/terraform-google-modules/terraform-docs-samples/tree/35e0c1909bec695a59fc1cd8e56c1a84781a8d27