個人的な印象:
注意点:
ssh -F ssh-config -fNg -L 5432:<RDS ID>.****.ap-northeast-1.rds.amazonaws.com:5432 bastion-host
Error: Error connecting to PostgreSQL server 127.0.0.1 (scheme: awspostgres): tls: failed to verify certificate: x509: cannot validate certificate for 127.0.0.1 because it doesn't contain any IP SANs
sudo vim /etc/hosts -- 127.0.0.1 <RDS ID>.****.ap-northeast-1.rds.amazonaws.com --
記事:
記事:
variable "with_optional_attribute" { type = object({ a = string # a required attribute b = optional(string) # an optional attribute c = optional(number, 127) # an optional attribute with default value }) }
記事:
記事:
上から順に読み込まれ、同名は上書きされる
Input Variables - Configuration Language | Terraform | HashiCorp Developer
記事:
variable "heroku_app_organization" { description = "" type = object({ name = string }) default = { name = null } } resource "heroku_app" "main" { ... dynamic "organization" { for_each = var.heroku_app_organization.name == null ? [] : [var.heroku_app_organization] content { name = organization.value.name } } }
sensitiveの変更を出力したい時:
記事:
... elasticsearch_configuration { domain_arn = try(each.value.es_cluster_endpoint, null) == null ? each.value.es_domain_arn : null cluster_endpoint = try(each.value.es_cluster_endpoint, null) ...
data "http" "example" { url = "https://example.com/api1" method = "POST" request_headers = { Content-Type = "application/json" } request_body = file("/path/to/example.json") } output "http_os_example_result" { value = "status_code: ${data.http.example.status_code}, response_body: ${data.http.example.response_body}" }
記事:
resource "elasticsearch_opensearch_ism_policy" "example" { policy_id = "example" body = templatefile( "${path.module}/files/opensearch/ism-policy-example.json", { min_index_age = "30d", min_size = "50gb", } ) }
記事:
for_each = toset(formatlist("%s", range(1, 10)))
locals { var1 = { bucket1 = { s3_bucket_arn = "arn:aws:s3:::example1" } bucket2 = { s3_bucket_arn = "arn:aws:s3:::example2" } } } data "aws_iam_policy_document" "s3" { statement { effect = "Allow" actions = [ "s3:GetObject", ] resources = concat([for value in local.var1 : value.s3_bucket_arn], [for value in local.var1 : "${value.s3_bucket_arn}/*"]) } }
rm versions.tf echo 'latest:^0.12' > .terraform-version rm -rf .terraform teraform init terraform 0.12upgrade teraform plan # コードのエラー修正
echo 'latest:^0.13' > .terraform-version rm -rf .terraform teraform init terraform 0.13upgrade teraform plan # コードのエラー修正 # applyしないとv1.0でエラーが出る teraform apply
echo 'latest:^1.0' > .terraform-version rm -rf .terraform teraform init teraform plan # コードのエラー修正
記事:
output example_password { value = aws_db_instance.example.password sensitive = true }
# json terraform output -json terraform output example_password # raw value terraform output -raw example_password
output example_password { value = nonsensitive(aws_db_instance.example.password) }
記事
例:
解決:
terraform untaint aws_db_instance.main
byte = "${100*1024*1024}" # 100MB
terraform plan -no-color > plan.txt
terraform console > concat(["a", "b"], ["c"]) [ "a", "b", "c", ] > exit
echo 'concat(["a", "b"], ["c"])' | terraform console
variable "mysql_57_parameters" { default = { character_set_server = "utf8" character_set_client = "utf8" } } resource "aws_db_parameter_group" "mysql_57" { name = "mysql-57" family = "mysql5.7" dynamic "parameter" { for_each = var.mysql_57_parameters content { name = parameter.key value = parameter.value } } }
terraform plan -target aws_db_parameter_group.mysql_57 ... # aws_db_parameter_group.mysql_57 will be created + resource "aws_db_parameter_group" "mysql_57" { + arn = (known after apply) + description = "Managed by Terraform" + family = "mysql5.7" + id = (known after apply) + name = "mysql-57" + name_prefix = (known after apply) + parameter { + apply_method = "immediate" + name = "character_set_client" + value = "utf8" } + parameter { + apply_method = "immediate" + name = "character_set_server" + value = "utf8" } }
variable "sg_ssh_ingress" { default = [ { cidr_blocks = ["xxx.xxx.xxx.xxx/32"] description = "ssh from AAA" security_groups = [] self = false }, { cidr_blocks = ["xxx.xxx.xxx.xxx/32"] description = "ssh from BBB" security_groups = [] self = false }, ... ] } resource "aws_security_group" "bastion" { ... dynamic "ingress" { for_each = var.sg_ssh_ingress content { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ingress.value.cidr_blocks security_groups = ingress.value.security_groups description = ingress.value.description self = ingress.value.self } } ... }
v0.12.6以降のループ処理について。
countの問題点:
Manage Similar Resources with For Each | Terraform - HashiCorp Learn:
locals { users = [ "user01", "user02", ] } resource null_resource user { for_each = toset(local.users) triggers = { user = each.value } }
terraform plan -target null_resource.user ... # null_resource.user["user01"] will be created + resource "null_resource" "user" { + id = (known after apply) + triggers = { + "user" = "user01" } } # null_resource.user["user02"] will be created + resource "null_resource" "user" { + id = (known after apply) + triggers = { + "user" = "user02" } }
index(local.users, each.value)
variable users { type = map(map(string)) default = { user01 = { home = "/home/user01" shell = "/bin/bash" } www01 = { home = "/var/www/html" shell = "/bin/false" } } } resource null_resource user { for_each = var.users triggers = { user = each.key home = lookup(each.value, "home") shell = lookup(each.value, "shell") } }
terraform plan ... # null_resource.user["user01"] will be created + resource "null_resource" "user" { + id = (known after apply) + triggers = { + "home" = "/home/user01" + "shell" = "/bin/bash" + "user" = "user01" } } # null_resource.user["www01"] will be created + resource "null_resource" "user" { + id = (known after apply) + triggers = { + "home" = "/var/www/html" + "shell" = "/bin/false" + "user" = "www01" } } Plan: 2 to add, 0 to change, 0 to destroy.
output "user_home" { value = [ for value in null_resource.user : value.home ] }
locals { # ses dkimのdataリソースで参照したほうが良いが、まだ存在しない ses_dkim = [ { "type" = "CNAME" "name" = "dummy1._domainkey.example.com" "value" = "dummy1.dkim.amazonses.com" }, { "type" = "CNAME" "name" = "dummy2._domainkey.example.com" "value" = "dummy2.dkim.amazonses.com" }, { "type" = "CNAME" "name" = "dummy3._domainkey.example.com" "value" = "dummy3.dkim.amazonses.com" }, ] } resource "aws_route53_record" "ses_dkim" { for_each = { for i in local.ses_dkim : i.name => i } zone_id = data.aws_route53_zone.example_com.zone_id name = each.value.name type = each.value.type ttl = "600" records = [each.value.value] }
find . -name .terraform -type d | xargs du -sh
# 対象ディレクトリ一覧 find . -name .terraform -type d # 削除実行 find . -name .terraform -type d | xargs -i rm -rf {}
リソースが増えてくると、planやapplyがどんどん遅くなる。
export TF_CLI_ARGS_plan="--parallelism=20" export TF_CLI_ARGS_apply="--parallelism=20"
v0.11とv0.12では挙動がまったく異なる場合がある。また、関数も入れ子だと動かない場合がある。
list[ list[ map { }, map { } ] ]
aws_instance.web[count.index].ebs_block_device[*].volume_id[*]
[count.index] # idが欲しい場合 id = aws_instance.web.*.id[count.index] # 一度localへ入れる場合 locals { web_ebs1 = "${flatten(aws_instance.web.*.ebs_block_device)}" } ... ${lookup(local.web_ebs1[count.index], "volume_id")}
Cycleエラーの解消などに利用できる。
EC2をN台作る等、同じ種類のリソースを複数定義する時に使える。
string01: string01 string02: string02 list01: - "aaa" - "bbb" dict01: key1: val1 key2: val2
output "external_yamldecode" { value = yamldecode(file("./vars.yml")) }
terraform init terraform plan terraform apply ... Outputs: external_yamldecode = { "dict01" = { "key1" = "val1" "key2" = "val2" } "list01" = [ "aaa", "bbb", ] "string01" = "string01" "string02" = "string02" }
#!/usr/bin/env python # -*- coding: utf-8 -*- # # read yaml file to json # # Requirements: # python 2.7 # pip install pyyaml from __future__ import print_function import sys import json import yaml from pprint import pprint def read_stdin(): return {x.strip() for x in sys.stdin} def main(): """ main """ try: stdin_lines = read_stdin() for line in stdin_lines: if line: jsondata = json.loads(line) f = open(jsondata["in_file"], "r") data = yaml.load(f, Loader=yaml.SafeLoader) f.close() sys.stdout.write(json.dumps(data)) except Exception, e: import traceback traceback.print_exc() sys.exit(1) if __name__ == '__main__': main() # vim: ts=4 sw=4 expandtab
echo '{"in_file":"./vars.yml"}' | ./read-from-yaml.py {"string02": "string02", "string01": "string01"}
data "external" "yaml" { program = ["python", "${path.module}/read-from-yaml.py"] query = { in_file = "./vars.yml" } } output "external_yaml" { value = "${data.external.yaml.result}" } output "external_yamldecode" { value = yamldecode(file("./vars.yml")) }
terraform init terraform plan terraform apply ... Outputs: external_yaml = { string01 = string01 string02 = string02 }
output "name" {value="..."}
data.terraform_remote_state.example.outputs.name
provider "heroku" { version = "= 1.9.0" ... }
terraform init -upgrade # or rm -rf .terraform terraform init
terraformはバージョンが0.0.1でも違うとコードの互換性が無くなる事が多い。
そのためバージョンを固定したい場合がある。
terraform { required_version = ">= 0.12.0" }
terraform { required_version = ">= 0.11.0, < 0.12.0" }
git clone https://github.com/tfutils/tfenv.git ~/.tfenv echo '[[ -d ~/.tfenv/bin ]] && export PATH="${PATH}:~/.tfenv/bin"' > ~/.bash.d/tfenv.sh chmod +x ~/.bash.d/tfenv.sh source ~/.bashrc # v0.11.xの最新をインストール tfenv install latest:^0.11 # カレントディレクトリをv0.11.xにする。 .terraform-version が書き換わる。~/で実行すればデフォルト値になる。 cd ~/ tfenv use 0.11.14
# 特定ディレクトリだけ0.12に変更 tfenv install latest:^0.12 # カレントディレクトリの .terraform-version が0.12.xへ変わる # 特定ディレクトリのバージョンを0.12.xへ固定 echo latest:^0.12 > .terraform-version
terraformはサイズが大きく、バージョンアップも頻繁なので古いバージョンを消したい。
tfenv list | grep -o -E '0.12.[0-9]+' | sort -n | head -n -1 | xargs -i bash -c 'tfenv uninstall {}'
terraform init This configuration or its associated state refers to the unqualified provider "aws".
terraform state replace-provider 'registry.terraform.io/-/aws' 'registry.terraform.io/hashicorp/aws'
echo latest:^0.13 > .terraform-version rm versions.tf terraform 0.13upgrade
terraform { required_version = ">= 0.13" required_providers { aws = { source = "hashicorp/aws" } } }
v0.11.x以下とコードの互換性が無くなかった。
terraform 0.12upgrade
同一のリソースを名前やアカウント、リージョンだけ作成したい時に、コードを共通化できる。
不便な点:
aws_elb "example" { ... depends_on = [ "module.example" ]
moduleを使わない方法:
記事:
provider "aws" { alias = "src" }
provider "aws" { alias = "usw1" region = "us-west-1" } module "example" { source = "../modules/tunnel" providers = { aws.src = "aws.usw1" } }
variable "var1" { default = "var1" } resource "aws_instance" "server" { ... } output "private_ip" { value = aws_instance.server.private_ip }
# 変数の参照 "${module.server.var1}" # 動的リソースの参照 "${module.server.private_ip}"
security_groups = ["${compact(list("sg-xxA", "sg-xxb", ""))}]"
# data.aws_caller_identity.current.arn = arn:aws:iam::123456789012:user/user01 locals { role_arn = "${replace(data.aws_caller_identity.current.arn, "/user\\/.+/", "")}role/example_global" }
戦略:
tables: key01: attr01: 10 key02: attr01: 20
variable "tables" { default = { "0" = "key01" "1" = "key02" } } output "tables.count" { value = "${length(var.tables)}" } # 2 output "tables.key0" { value = "${lookup(var.tables, 0)}" } # key01 variable "tables_attr01" { default = { "key01" = "10" "key02" = "20" } } output "tables_attr01.key01.val.way1" { value = "${lookup(var.tables_attr01, "key01")}" } # 10 output "tables_attr01.key01.val.way2" { value = "${lookup(var.tables_attr01, lookup(var.tables, 0))}" } # 10
terraform apply ... Outputs: tables.count = 2 tables.key0 = key01 tables_attr01.key01.val.way1 = 10 tables_attr01.key01.val.way2 = 10
CONDITION ? TRUEVAL : FALSEVAL
# moduleを同一アカウントで、複数回使う場合、2回目以降はfalseにしておく。 variable create_iam_role { default = true } resource "aws_iam_role" "example_global" { count = "${var.create_iam_role ? 1 : 0}" name = "example_global" ...
resource "aws_instance" "vpn" { count = "${var.something ? 1 : 0}" }
# example: ["arn:aws:iam::1234567890:root", "..."] variable account_id_arns { default = [] } # example: ["arn:aws:s3:::example1, "..."] variable s3_r_bucket_arns { default = [] } variable aws_iam_role_name { default = "assume-role-s3" } resource "aws_iam_role" "assume-role-s3" { name = "${var.aws_iam_role_name}" assume_role_policy = "${data.aws_iam_policy_document.assume-role.json}" } resource "aws_iam_role_policy" "s3-r" { name = "s3-r" role = "${aws_iam_role.assume-role-s3.id}" policy = "${data.aws_iam_policy_document.s3-r.json}" count = "${ length(var.allowed_r_s3_bucket_arns) > 0 ? 1 : 0 }" } data "aws_iam_policy_document" "assume-role" { # assume role statement { actions = [ "sts:AssumeRole", ] effect = "Allow" principals = { type = "AWS" identifiers = "${var.account_id_arns}" } } } data "aws_iam_policy_document" "s3-r" { statement { actions = [ "s3:ListBucket", ] resources = ["${var.s3_r_bucket_arns}"] } statement { actions = [ "s3:GetObject", ] resources = ["${split( ",", "${join("/*,", var.s3_r_bucket_arns)}/*" )}"] } }
terraform fmt
output "web-0001.public_dns" { value = "${aws_instance.web.1.public_dns}" }
output "web.public_dns" { value = "${aws_instance.web.*.public_dns}" }
subnet_id = element(data.aws_subnet_ids.main_private.ids[*], count.index)
aws_instance.web.id[count.index]
stateはbackend指定でS3に置けるが、通常default一つしか無い。
dev, stg, prodのように分けてstateを管理できる。
awsのregion毎に分けても良い。
terraform workspace new dev
terraform workspace select dev
variable "cidr_whitelist" { type = "list" default = [ { "value" = "${var.cidr_office}" type = "IPV4" }, ]
locals { cidr_whitelist = [ { "value" = "${var.cidr_office}" type = "IPV4" }, ] }
locals { owner = "taro.yamada" site_id = "example-dev" } locals { common_tags = { owner = local.owner site_id = local.site_id } } aws_instance web { ... tags = merge(local.common_tags, { Name = "${local.prefix}-web-01" role = "web" }) }
配列の要素の末尾に文字列を結合したい場合がある。
data "aws_iam_policy_document" "s3-r" { statement { actions = [ "s3:ListBucket", ] resources = ["${var.s3_r_bucket_arns}"] } statement { actions = [ "s3:GetObject", ] resources = ["${split( ",", "${join("/*,", var.s3_r_bucket_arns)}/*" )}"] } }
data "aws_instances" "web" { instance_tags = { "Name" = "web-*" } instance_state_names = ["running", "stopped"] } # 参照する場合 "${split( ",", "${join("/32,", data.aws_instances.web.public_ips)}/32")}",
Terraformで起動、チェック実行、破棄までを自動化
リソースの作成/削除等で、timeoutする場合がある。
resource "aws_db_instance" "timeout_example" { name = "mydb" # ... timeouts { create = "60m" delete = "2h" } }
中国のalicloud用プロバイダー
resource "aws_iam_policy" "policy" { name = "test_policy" path = "/" description = "My test policy" policy = <<EOT { "Version": "2012-10-17", "Statement": [ { "Action": [ "ec2:Describe*" ], "Effect": "Allow", "Resource": "*" } ] } EOT }
# https://aws.amazon.com/marketplace/fulfillment?productId=b7ee8a69-ee97-4a49-9e68-afaee216db2e&ref=cns_srchrow&versionTitle=1708 # "${lookup(var.aws_ami_ids, "ap-northeast-1_centos7_2017-10-12")}" variable "aws_ami_ids" { default = { "us-east-1_centos7_2017-10-12" = "ami-db48ada1" "us-west-2_centos7_2017-10-12" = "ami-e535c59d" "ap-southeast-1_centos7_2017-10-12" = "ami-1fbad07c" "ap-northeast-1_centos7_2017-10-12" = "ami-e4599a82" "eu-central-1_centos7_2017-10-12" = "ami-2540f74a" "eu-west-1_centos7_2017-10-12" = "ami-5f76b626" } } variable "aws_region" { default = "ap-northeast-1" } locals { aws_ami_id_centos7 = "${lookup(var.aws_ami_ids, "${var.aws_region}_centos7_2017-10-12")}" } output "aws_ami_ids" { value = "${var.aws_ami_ids}" } output "aws_ami_id_centos7" { value = "${local.aws_ami_id_centos7}" }
terraform apply ... aws_ami_id_centos7 = ami-e4599a82 aws_ami_ids = { ap-northeast-1_centos7_2017-10-12 = ami-e4599a82 ap-southeast-1_centos7_2017-10-12 = ami-1fbad07c eu-central-1_centos7_2017-10-12 = ami-2540f74a eu-west-1_centos7_2017-10-12 = ami-5f76b626 us-east-1_centos7_2017-10-12 = ami-db48ada1 us-west-2_centos7_2017-10-12 = ami-e535c59d
terraform state list terraform state rm <terraform address> terraform import <terraform address> <resource id>
cat var.tf -- variable api_key {} -- api_key=*** terraform plan -var "api_key=$api_key"
api_key = "****"
security_groups = ["sg-xxxx1", "${var.list_sg}"]
EBSを追加したり、SGを追加しようとするとEC2を作り直してしまう場合がある。
lifecycle { ignore_changes = ["root_block_device"] }
TF_LOG=DEBUG terraform plan
public dns, IP等、後で参照したい情報を出力するのに便利。
refreshまたはapplyした後でしか表示されない。(tfstateファイルの中身を表示しているのでは)
terraform output # json terraform output -json
terraform output -json example_domain_validation_options | jq -r ".[] | [.resource_record_name, .resource_record_type, .resource_record_value] | @tsv" _1234567890abcdef1234567890abcdef.example.com CNAME _1234567890abcdef1234567890abcde.abcdefghij.acm-validations.aws.
Output Variablesでは1つの値しか出力できないようだ。
output "web.private_ip" { value = "${aws_instance.web.*.private_ip}" } Outputs: web.private_ip = [ i-1111, i-2222 ]
output "web.private_ip" { value = "${join(",",aws_instance.web.*.private_ip)}" }
terraform { backend "s3" { profile = "myprofile" # shared_credentials_file = "~/.aws/config" # v0.9.3 で試しても失敗 region = "ap-northeast-1" bucket = "mybucket" key = "web/terraform.tfstate" # path/file で1バケットに複数のtfstateを置ける } }
aws --profile myprofile configure # ~/.aws/credentials に該当キーが出来ているのが重要。 terraform init terraform plan
terraform plan -target=aws_instance.web[0]
cat example.tf| perl -ane 'if(/resource\s+"([^\"]+)"\s+"([^\"]+)"/){print "-target $1.$2 \\\n";}'
# example.tf variable "security_groups" { default = ["sg-xx", "sg-x,sg-xxx"] } resource "aws_instance" "web" { ... security_groups = "${var.security_groups}" }
# example.tf variable "security_groups" { default = "sg-xx,sg-x,sg-xxx" } resource "aws_instance" "web" { ... security_groups = "${split(",", var.security_groups)}" }
※terraform v0.7.0からimport機能が実装されたため、Terraformingは不要かもしれない。
terraform.tfstate や tf形式のソースを生成する [#f45434d2]
rvm use 2.1.5 ruby --version ruby 2.1.5p273 (2014-11-13 revision 48405) [x86_64-linux] rvmsudo gem install terraforming aws-sdk ln -s ~/.aws/config ~/.aws/credentials # なぜか --profileオプションが動作せず export AWS_ACCESS_KEY_ID=**** export AWS_SECRET_ACCESS_KEY=**** export AWS_REGION=ap-northeast-1 terraforming ec2 > terraforming.ec2.tf terraforming ec2 --tfstate >terraforming.terraform.tfstate
TERRAFORM_VER=0.8.5 sudo mkdir -p /opt/terraform.${TERRAFORM_VER} sudo wget -O /opt/terraform.${TERRAFORM_VER}/terraform_${TERRAFORM_VER}_linux_amd64.zip https://releases.hashicorp.com/terraform/${TERRAFORM_VER}/terraform_${TERRAFORM_VER}_linux_amd64.zip sudo unzip -d /opt/terraform.${TERRAFORM_VER}/ /opt/terraform.${TERRAFORM_VER}/terraform_${TERRAFORM_VER}_linux_amd64.zip # v0.7.x以上の場合 sudo alternatives --install /usr/local/bin/terraform terraform /opt/terraform.${TERRAFORM_VER}/terraform 70 # v0.6.x以下の場合 echo alternatives --install /usr/local/bin/terraform terraform /opt/terraform.${TERRAFORM_VER}/terraform 60 \\ > /tmp/install-terraform.sh find /opt/terraform.${TERRAFORM_VER}/ -name 'terraform-*' -type f -printf ' --slave /usr/local/bin/%f %f %p \\\n' >> /tmp/install-terraform.sh sudo bash /tmp/install-terraform.sh
alternatives --display terraform sudo alternatives --set terraform /opt/terraform.${TERRAFORM_VER}/terraform # 削除 sudo alternatives --set terraform /opt/terraform.${TERRAFORM_VER}/terraform
resource "aws_iam_policy_attachment" "s3-rw" { name = "s3-jp-rw" users = ["s3-jp-rw","s3-us-rw"] policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess" }
wget -O terraform_0.3.7_linux_amd64.zip https://dl.bintray.com/mitchellh/terraform/terraform_0.3.7_linux_amd64.zip sudo unzip terraform_0.3.7_linux_amd64.zip -d /usr/local/bin/ terraform version Terraform v0.3.7