在使用Terraform来运行AWS环境时,所遇到的问题及其解决方法
2017年3月時点においてのメモです。Terraformのバージョンは0.8.6でしたが、現在のバージョンはTerraform v0.9.0-dev(github/master)です。
想要使用Terraform的最新版本。
为难的事情 (Komat ta koto)
我很想尽快使用0.9版本的功能。
解决办法
好像可以很容易地从GitHub构建。Go语言很厉害。
$ brew uninstall terraform
$ brew install go
$ cat <<'EOS' >> ~/.bashrc
export PATH=$(go env GOPATH)/bin:$PATH
EOS
$ go install github.com/hashicorp/terraform
$ terraform -v
Terraform v0.9.0-dev
有没有什么好的参考代码?
困扰之事 zhī shì)
不只是想要参考资料,还想要看一些示例或样本代码。
策略的解决
在GitHub上,有一个公式的最佳实践。
还有,这里可能也会有所参考价值。
https://github.com/terraform-community-modules
处理AWS账户的身份验证信息
困扰的事情
根据网络上的参考信息,有很多示例都直接将AWS的认证信息(credentials)写入代码中。然而,如果采取修改tf文件的运维方式,很可能会在某个时候出错,不小心提交和推送到版本控制系统中,导致事件发生。
我认为用于首次运行Terraform的用户认证信息通常具有很高的权限,因此我们需要谨慎对待。
解决方案
使用AWS-CLI的命名配置文件。
$ aws configure --profile my-profile-name
AWS Access Key ID [None]: xxxxxxxxxx
AWS Secret Access Key [None]: xxxxxxxxxx
Default region name [None]: ap-northeast-1
Default output format [None]:
如果事先进行这样的设置,认证信息将以命名方式存储在$home/.aws/credentials中。
从脚本中可以这样引用。这样的话即使进行提交也没有问题。
provider "aws" {
profile = "my-profile-name"
}
只需按照上述的设置步骤将其写入README.md文件中,然后正常管理KEYID和SECRET即可。
.tfstate文件的处理方式
困扰
如果丢失.tfstate文件,我们将无法继续通过Terraform管理所创建的实例,所以它是最重要的文件。
然而,按照将其提交到git的规定,担心会忘记提交或忘记进行pull时会很麻烦。
另一方面,即使手动在各个地方进行处理,也可能会出现其他开发者基于旧状态创建重复的实例的情况。
解决方案
让我们使用后端S3。这是Terraform 0.9版本的新功能。
terraform {
backend "s3" {
bucket = "my-tfstate-store-name-at-s3"
key = "hogehoge/terraform.tfstate"
profile = "my-profile-name"
}
}
说在前面,只需运行terraform init,就可以同步S3的tfstate。
请务必将指定的存储桶设置为acl=private!
此外,推荐在上述链接中添加S3的版本控制等设置。(我没有做)
除了S3之外,似乎还准备了各种手段。
这样就可以正常使用git进行管理了。
之前保存在.terraform/terraform.tfstate的数据会直接保存在S3上的文件中,不再进行本地缓存,所以要注意。
解决方法(过时)
让我们使用Backend中的远程状态功能。
有正式的官方文档。https://www.terraform.io/docs/state/remote/s3.html
这样子。因为可以指定个人资料,所以可以在那里写上地区以简化。
$ terraform remote config \
-backend=s3 \
-backend-config="bucket=my-tfstate-store-name-at-s3" \
-backend-config="key=hogehoge/terraform.tfstate" \
-backend-config="profile=my-profile-name"
在执行此命令时,已存在的terraform.tfstate似乎被移动到./.terraform/terraform.tfstate。
然后,它会自动上传和下载tfstate文件。
请确保指定的存储桶为acl=private!
建议在保存的存储桶上添加版本控制等功能。(我没有这样做)
除了S3,似乎还提供了各种保存选项。
对不同环境中的变量进行处理。
困った事情
(Komatagatta jijō)
我想要像分开暗中操作和正式操作那样,为每个环境设置不同的变量数值。但是,用-var选项在参数中全部指定太麻烦了。
解决方案 (jiě jué cè)
如果将共通配置写在名为terraform.tfvars的文件中,即使不指定也会自动读取。
根据不同的环境,可以使用-var-file选项来进行切换。
$ terraform plan -var-file staging.tfvars
$ terraform plan -var-file production.tfvars
很普通。
只要巧妙地使用,据说var后面指定的内容将会被覆盖,所以应该会很强大。
想要使用Packer创建的AMI生成EC2实例。
困惑的事情 de
当考虑到自动伸缩时,我希望能够正确运营起源的AMI。
(注意,我可能对AWS的自动伸缩不太了解。)
所以,如果使用Packer创建一个不可变的镜像,那将会非常棒。
不可变意味着镜像中必须包含DB连接信息。
因此,需要先使用Terraform来创建RDS,但是Terraform又需要使用AMI,所以就出现了这样的问题…
造成了循环引用。
一开始,在替换AutoScaling下的实例时,要确保不关闭所有的实例,似乎相当困难。
看来AutoScaling与之相容性有待改善。
Reference: http://qiita.com/minamijoyo/items/e32eaeebc906b7e77ef8
参考资料:http://qiita.com/minamijoyo/items/e32eaeebc906b7e77ef8
可能的策略1 空资源解决方案
最终虽然没有成功,但我最初尝试的方法。
有一种叫做 null_resource 的东西。
可以定義代表不進行任何操作的觸發(triggers)屬性和能夠配置provisioner的資源。
variable "ami_name_prefix" {}
resource "null_resource" "packer" {
triggers {
db_host = "${aws_db_instance.hogehoge.address}"
}
provisioner "local-exec" {
command = <<EOS
packer build \
-var 'DB_HOST=${aws_db_instance.hogehoge.address}' \
-var 'AMI_NAME_PREFIX=${ami_name_prefix}' \
packer.json"
EOS
}
}
data "aws_ami" "packer_ami" {
most_recent = true
name_regex = "^${var.ami_name_prefix}.*"
owners = ["self"]
depends_on = ["null_resource.packer"]
}
resource "aws_instance" "hoge" {
ami_id = "${data.aws_ami.packer_ami.id}"
...
}
就是这种感觉。
然而,使用Terraform 0.8.6版本时,将内插表达式与触发器混合使用会被强制认定为”computed”,导致每次都会创建AMI。从Terraform的角度来看,无法仅执行差异操作,因此无法使用该方式。
放棄不變的解決方案
自动缩放本身在这种情况下可能不可行吗?我认为需要重新进行AMI化。
预先组装好几乎完成的AMI并使用aws_instance的provisioner来完成最后的修改。听说Docker之类的东西可以通过环境变量来读取外部信息,所以我决定采用这种方法。
(2017/3 注:如果使用AWS EC2,可以使用user_data机制。使用这个机制可能更简单。感谢您在评论中指出。)
// .envが欠けた状態でAMIをつくる
$ packer packer.json
data "aws_ami" "packer_ami" {
most_recent = true
name_regex = "^packer-ami-.*$"
owners = ["self"]
depends_on = ["null_resource.packer"]
}
data "template_file" "envfile" {
# template = "${file(./env.tpl)}" などとした方が望ましいが例示のため。
template = <<EOS
export DB_HOST="${db_host}"
export DB_PORT="${db_port}"
....
EOS
vars {
db_host = "${aws_db_instance.main.address}"
db_port = "${aws_db_instance.main.port}"
}
}
resource "aws_instance" "ec2" {
ami_id = "${data.aws_ami.packer_ami.id}"
...
# envファイルをuploadする。envファイルはdirenvとかdotenvとかで読み込まれるようにしておく。
provisioner "file" {
content = "${data.template_file.envfile.rendered}"
destination = "/home/ec2-user/.env"
}
# 上で入れた環境変数を使ってサービスを立ち上げる。
provisioner "remote-exec" {
inline = ["sudo service unicorn start"]
}
# provisionerは、実行した環境からssh(のgolang実装。sshコマンドではない)で接続することになる。
# 当然security_groupとvpc_internet_gatewayが適切に設定されている必要がある。
connection {
type = "ssh"
...
}
}
我想要对tf文件进行结构化处理。
无法解决的问题
抄襲和重复是不好的,而且降低了再利用性。
计算
你可以使用COUNT = n来创建多个具有相同类型的资源,就像创建数组一样。
如果你想要给每个资源写入稍微不同的值,
COUNT = 2
ATTR = "${element(list("a", "b", "c", "d"), count.index)}"
可以进行诸如此类的操作。
把list变成变量会更好。
模块 (mó )
如果涉及多个资源的重复模式,最好将其模块化。
关于模块的具体编写方式在此不做解释。
./modules/my_module/variables.tf
./modules/my_module/main.tf
./modules/my_module/output.tf
./模块/我的模块/变量.tf
./模块/我的模块/主要.tf
./模块/我的模块/输出.tf
制造
module "use_of_my_module" {
source = "./my_module"
var1 = ...
var2 = ...
}
用来书写。
$ terraform get
然后,让我们准备好将模块加载到terraform中。(好像在./.terraform/modules/中创建了一个符号链接)
由于似乎有各种不同的模块可供公开使用,也许更好的选择是不自己编写资源。
其他
如果有任何问题,请写下来。