Memo/Terraform

http://dexlab.net/pukiwiki/index.php?Memo/Terraform
 

Terraform

  • https://www.terraform.io/
    • CHANGELOG.md
    • HashiCorp?製ツール(Vagrant, Packer, Serf等の開発元)
    • AWS, Heroku等の複数のクラウドサービスに対応している
    • Terraformの不具合の多さを理解しつつ、Terraformの不具合報告等のオープンソース活動を行える余裕がある人向け。
    • https://github.com/hashicorp/terraform/issues を見ると分かるが1000 issue以上が作成されており、不具合の修正が間に合っていない印象
    • 状態をステートファイル(terraform.tfstate)に持ち、これを元に差分更新する。
      • 無くすと大変。更新ができなくなる。
      • ステートファイルはAWS S3等に置く事はできる。
      • ステートファイルの管理が面倒。リソースの差分がうまく比較できないようで、リソースの更新には不向き。新規作成・破棄を繰り返す用途向き
    • ドキュメント・サンプルがほとんど無い。α版の印象
    • マイナーバージョンが0.0.1変わっても、仕様が大きく変わり、互換性が無くなる事も多い。
      • 開発方針がdevしかなく、stable等の安定版が無い
    • AWS:対応しているリソースの新規立ち上げは楽だが、非対応の項目がかなりあるため、自分が必要としている機能をサポートしているか要確認
    • 遭遇したトラブル
      • v0.0.1上がるとコードに無いheroku configを消すように仕様変更
      • API GatewayのSSL証明書の登録が成功したように見えるが、実は成功していない。
      • destroy時は削除確認があるが、EC2セキュリティグループの変更で、EC2が作り直される時には警告はない。plan時に予期しない変更が無いか強く確認が必要。さもなければ簡単にリソースが消える
      • セキュリティグループに変更を加えると、EC2を作り直そうとする
      • ELBにリスナー追加しようとすると、リスナー全て消えた
      • SSL証明書のアップロードが成功したように見えて壊れている
      • EC2にEBSを追加しようすると、EC2を作り直そうとする

terraform-landscape: planの結果を見やすく


配列の要素の末尾に文字列を追加

配列の要素の末尾に文字列を結合したい場合がある。

  • 元変数: ["arn:aws:s3:::example1","arn:aws:s3:::example2"]
  • 求める出力: ["arn:aws:s3:::example1/*","arn:aws:s3:::example2/*"]
  • Terraform v0.11.7
  • 例:Amazon S3: 特定の S3 バケットへの読み取りと書き込みアクセスを許可する のポリシーを生成したい場合
    • File not found: "terraform.example.multiple-resources.zip" at page "Memo/Terraform"[添付]
      1. terraform init
      2. terraform plan -var aws_profile=example
      3. terraform apply -var aws_profile=example
      4. ...
      5. Outputs:
      6. aws_iam_policy_document.s3_bucket_policy.json = {
      7.   "Version": "2012-10-17",
      8.   "Statement": [
      9.     {
      10.       "Sid": "",
      11.       "Effect": "Allow",
      12.       "Action": "s3:ListBucket",
      13.       "Resource": [
      14.         "arn:aws:s3:::example2",
      15.         "arn:aws:s3:::example1"
      16.       ]
      17.     },
      18.     {
      19.       "Sid": "",
      20.       "Effect": "Allow",
      21.       "Action": [
      22.         "s3:PutObject",
      23.         "s3:GetObject"
      24.       ],
      25.       "Resource": [
      26.         "arn:aws:s3:::example2/*",
      27.         "arn:aws:s3:::example1/*"
      28.       ]
      29.     }
      30.   ]
      31. }

ランダムID/文字列の生成

RDSのroot userパスワード等に使える。


Terratest: インフラ自動テストツール

Terraformで起動、チェック実行、破棄までを自動化


タイムアウトの延長

リソースの作成/削除等で、timeoutする場合がある。

  • Timeouts
  • aws_db_instanceの場合
    1. resource "aws_db_instance" "timeout_example" {
    2.   name              = "mydb"
    3.   # ...
    4.   timeouts {
    5.     create = "60m"
    6.     delete = "2h"
    7.   }
    8. }

alicloud(aliyun alibaba)

中国のalicloud用プロバイダー


ヒアドキュメントでJSON等を綺麗に書く

  • terraformでは引用が「"」のみで、JSON等を書こうとすると、「\"」のエスケープが必要で見難い
  • JSON Formatter & Validator JSONの検証と整形
  • ヒアドキュメント「var = <<EOF 〜 EOF」が使える
    1. resource "aws_iam_policy" "policy" {
    2.     name = "test_policy"
    3.     path = "/"
    4.     description = "My test policy"
    5.     policy = <<EOF
    6. {
    7.   "Version": "2012-10-17",
    8.   "Statement": [
    9.     {
    10.       "Action": [
    11.         "ec2:Describe*"
    12.       ],
    13.       "Effect": "Allow",
    14.       "Resource": "*"
    15.     }
    16.   ]
    17. }
    18. EOF
    19. }

変数にmap(連想配列)を使う

  • type = "map" は省略できる
  • aws.tf
    1. # https://aws.amazon.com/marketplace/fulfillment?productId=b7ee8a69-ee97-4a49-9e68-afaee216db2e&ref=cns_srchrow&versionTitle=1708
    2. # "${lookup(var.aws_ami_ids, "ap-northeast-1_centos7_2017-10-12")}"
    3. variable "aws_ami_ids" {
    4.   default = {
    5.     "us-east-1_centos7_2017-10-12"      = "ami-db48ada1"
    6.     "us-west-2_centos7_2017-10-12"      = "ami-e535c59d"
    7.     "ap-southeast-1_centos7_2017-10-12" = "ami-1fbad07c"
    8.     "ap-northeast-1_centos7_2017-10-12" = "ami-e4599a82"
    9.     "eu-central-1_centos7_2017-10-12"   = "ami-2540f74a"
    10.     "eu-west-1_centos7_2017-10-12"      = "ami-5f76b626"
    11.   }
    12. }
    13.  
    14. output "aws_ami_ids" { value = "${var.aws_ami_ids}" }
    15. output "aws_ami_id_centos7" { value = "${lookup(var.aws_ami_ids, "ap-northeast-1_centos7_2017-10-12")}" }
  • 実行結果
    1. terraform apply
    2. ...
    3. aws_ami_id_centos7 = ami-e4599a82
    4. aws_ami_ids = {
    5.   ap-northeast-1_centos7_2017-10-12 = ami-e4599a82
    6.   ap-southeast-1_centos7_2017-10-12 = ami-1fbad07c
    7.   eu-central-1_centos7_2017-10-12 = ami-2540f74a
    8.   eu-west-1_centos7_2017-10-12 = ami-5f76b626
    9.   us-east-1_centos7_2017-10-12 = ami-db48ada1
    10.   us-west-2_centos7_2017-10-12 = ami-e535c59d

import: 既存のリソースからtfstateを作成する

GitHub - dtan4/terraforming? でやっていた事が公式で一部可能になった。

  • Command: import - Terraform by HashiCorp
    • 既存リソースからtf, tfstateファイルを生成できる
    • リソースIDを一つ一つ指定しなければいけないので面倒
    • v0.9.4: route53をimportしようとするとcrashした

変数

  • パスワード等はtfファイルに書かないようにしたい場合。「-var "key=value"」が複数使える
    1. cat rds.tf
    2. variable db_root_password {}
    3.  
    4. db_root_password=$(mkpasswd -l 16 -s 0)
    5. terraform plan -var "db_root_password=$db_root_password"
  • 別ファイルにしたい場合: 「-var-file="secret.tfvars"」

バージョン変更によるアップグレード方法


VPCの作成


lifecycle: 指定リソースの差分を無視する

EBSを追加したり、SGを追加しようとするとEC2を作り直してしまう場合がある。

  • Configuring Resources - Terraform by HashiCorp
    • create_before_destroy: 既存リソースが有った場合、削除してから作成する
    • prevent_destroy: リソース保護。削除する時にエラーになる
    • ignore_changes: 差分があっても無視する
  • 後から追加したroot_block_deviceの差分を無視したい。
    1. lifecycle {
    2.   ignore_changes = ["root_block_device"]
    3. }

AWS


aws_nat_gateway

  • subnet毎にnat-gatewayを作成
  • terraform v0.10.0
    1. variable aws_subnets {
    2.   type = "list"
    3.   default = [
    4.     "subnet-aaaa",  # AZ: ap-northeast-1a
    5.     "subnet-cccc",  # AZ: ap-northeast-1c
    6.   ]
    7. }
    8.  
    9. resource "aws_nat_gateway" "gw" {
    10.   allocation_id = "${aws_eip.gw.*.id[count.index]}"
    11.   subnet_id     = "${var.aws_subnets[count.index]}"
    12.   count         = "${length(var.aws_subnets)}"
    13. }
    14.  
    15. resource "aws_eip" "gw" {
    16.   vpc   = true
    17.   count = "${length(var.aws_subnets)}"
    18. }

複数リソースの指定

  • EC2の数がsubnet数を超えても、AZが正しく振られるようにする
  1. variable "ec2_subnets" { default = ["subnet-xxxx","subnet-xxxx","subnet-xxxx"] }
  2.  
  3. resource "aws_instance" "web" {
  4. ...
  5.   count     = 4
  6.   subnet_id = "${var.ec2_subnets[count.index % length(var.ec2_subnets)]}"
  7. }
  • 変数と配列を指定 v0.10.2
    1. variable "cidr_common" { default = "192.168.2.1/32" }
    2. variable "cidr_example" { default = ["192.168.1.0/24", "10.5.1.0/24"] }
    3.  
    4. resource "aws_security_group" "common" {
    5. ...
    6.   cidr_blocks = [
    7.     "${var.cidr_common}",
    8.     "${var.cidr_example}"
    9.   ]
  • EC2インスタンスにEIPを付ける v0.10.2
    1. variable "instance_count_web"       { default = 1 }
    2.  
    3. # aws_elbは複数リソースがそのまま指定できる
    4. resource "aws_elb" "frontend" {
    5.     instances = ["${aws_instance.web.*.id}"]
    6. }
    7.  
    8. # aws_eipは一つのリソースしか指定できないので変換して渡す
    9. resource "aws_eip" "web" {
    10.     count = "${var.instance_count_web}"
    11.     instance = "${element(aws_instance.web.*.id, count.index)}"
    12.     vpc = true
    13. }
  • Resource '〜' not found for variable '〜.1.id': 「.0.id」はアクセスできるが、「.1.id」がエラーになった。terraformの不具合か分からないが、以下の様に明示的に指定できる
    1. "${element(aws_instance.web.*.id, 1)}"

RDS

AWS: aws_db_instance - Terraform by HashiCorp

  • classic network上にRDSを作る場合にsubnetのエラーが出る。v0.8.6で確認
    • 「publicly_accessible = true」が必要

デバッグ

  • デバッグログを出す
    1. TF_LOG=DEBUG terraform plan

output: 出力だけを見る

public dns, IP等、後で参照したい情報を出力するのに便利

  1. terraform output

outputで複数リソースの値を出力

Output Variablesでは1つの値しか出力できないようだ。

  • いつのバージョンからかJSONで出力できるようになっていた
    1. output "web.private_ip" { value = "${aws_instance.web.*.private_ip}" }
    2.  
    3. Outputs:
    4. web.private_ip = [
    5.     i-1111,
    6.     i-2222
    7. ]
  • 複数EC2のprivate ipをカンマ区切りで出力
    1. output "web.private_ip" { value = "${join(",",aws_instance.web.*.private_ip)}" }

backend: : S3等に変数を置く

  • terraform.tf:
    • このファイル内で変数は使えなかった。「configuration cannot contain interpolations」エラー
      1. terraform {
      2.   backend "s3" {
      3.     profile = "myprofile"
      4. #    shared_credentials_file = "~/.aws/config" # v0.9.3 で試しても失敗
      5.     region  = "ap-northeast-1"
      6.     bucket  = "mybucket"
      7.     key     = "web/terraform.tfstate" # path/file で1バケットに複数のtfstateを置ける
      8.   }
      9. }
  • 実行: terraform v0.9.3
    1. aws --profile myprofile configure
    2.  
    3. # ~/.aws/credentials に該当キーが出来ているのが重要。
    4.  
    5. terraform init
    6.  
    7. terraform plan

REMOTE STATE: S3等に変数を置く

  • terraform_remote_state
    • v0.6.14,15ではうまく動作しなかった。terraform.tfstate にユーザのAWS KEYSが入ってしまうので注意。
    • v0.7で廃止。

REMOTE CONFIG: S3等にterraform.tfstateを置く

terraform.tfstate にAWS KEYSやパスワード等が入っているため、git/svnにコミットしにくい。
S3等にpushする機能がある。

  • 「terraform.tfstate」がS3のバケットにアップロードされる。localの「terraform.tfstate」は「.terraform/terraform.tfstate」に移動する。
    1. export AWS_ACCESS_KEY_ID=<YOUR ACCESS KEY>
    2. export AWS_SECRET_ACCESS_KEY=<YOUR SECRET KEY>
    3. terraform remote config \
    4.  -backend=S3 \
    5.  -backend-config="region=ap-northeast-1" \
    6.  -backend-config="bucket=mybucket-terraform" \
    7.  -backend-config="key=terraform.tfstate"
  • localにある「terraform.tfstate」をremoteにpushする
    1. terraform remote push
  • remoteにある「terraform.tfstate」をlocalに持ってくる
    1. terraform remote pull
  • 無効化する。localの「terraform.tfstate」が復活する。remoteのファイルは消えなかった
    1. terraform remote config -disable

指定したリソースだけplan/apply

  • 「-target=resource 」オプションがある。複数指定したい場合は「-target=resource1 -target=resource2」とする
    1. terraform plan -target=aws_instance.web[0]
  • Command: plan - Terraform by HashiCorp

変数で配列が扱えない

  • 変数に配列が使用できない。(v0.6.6で確認) v0.7以降で使用可能
  • [v0.7以降]
    1. # example.tf
    2. variable "security_groups" {                                                                                                                                                                                                             
    3.   default = ["sg-xx", "sg-x,sg-xxx"]
    4. }
    5.  
    6. resource "aws_instance" "web" {
    7.   ...
    8.   security_groups = "${var.security_groups}"
    9. }
  • Store arrays in variables Issue #57 hashicorp/terraform GitHubで要望は上がっているようだ。
  • [v0.7未満] 擬似的に配列を設定する
    1. # example.tf
    2. variable "security_groups" {                                                                                                                                                                                                             
    3.   default = "sg-xx,sg-x,sg-xxx"
    4. }
    5.  
    6. resource "aws_instance" "web" {
    7.   ...
    8.   security_groups = "${split(",", var.security_groups)}"
    9. }

Terraforming: 既存のインフラを Terraform で管理できるように

terraform v0.7.0からimport機能が実装されたため、Terraformingは不要かもしれない。

terraform.tfstate や tf形式のソースを生成する [#f45434d2]

  • CentOS6.xの場合。ruby 2.1.0以上なので、rbenvやrvmで新しいrubyを使えるようにする
    1. rvm use 2.1.5
    2. ruby --version
    3. ruby 2.1.5p273 (2014-11-13 revision 48405) [x86_64-linux]
    4.  
    5. rvmsudo gem install terraforming aws-sdk
    6. ln -s ~/.aws/config ~/.aws/credentials
    7.  
    8. # なぜか --profileオプションが動作せず
    9. export AWS_ACCESS_KEY_ID=****
    10. export AWS_SECRET_ACCESS_KEY=****
    11. export AWS_REGION=ap-northeast-1
    12. terraforming ec2 > terraforming.ec2.tf
    13. terraforming ec2 --tfstate >terraforming.terraform.tfstate

複数バージョンの切り替え

  • バージョンによって動作しない機能があり、古いバージョンに切り替えたい時がある。
    • 0.7.1: terraformコマンドが1ファイルになった
    • 0.6.0: CentOS6.x ReleaseMedia?が起動しないのが直った?
    • 0.5.3: AWS EC2でCentOS6.x ReleaseMedia?が起動しない
    • 0.3.7: 1回目は成功するが、2回目は、AWS EC2セキュリティグループが壊れる
  • CentOS6.x 64bitの場合
    1. TERRAFORM_VER=0.8.5
    2. sudo mkdir -p /opt/terraform.${TERRAFORM_VER}
    3. 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
    4. sudo unzip -d /opt/terraform.${TERRAFORM_VER}/ /opt/terraform.${TERRAFORM_VER}/terraform_${TERRAFORM_VER}_linux_amd64.zip
    5.  
    6. # v0.7.x以上の場合
    7. sudo alternatives --install /usr/local/bin/terraform terraform /opt/terraform.${TERRAFORM_VER}/terraform 70
    8.  
    9. # v0.6.x以下の場合
    10. echo alternatives --install /usr/local/bin/terraform terraform /opt/terraform.${TERRAFORM_VER}/terraform 60 \\ > /tmp/install-terraform.sh
    11. find /opt/terraform.${TERRAFORM_VER}/ -name 'terraform-*' -type f -printf ' --slave /usr/local/bin/%f %f %p \\\n' >> /tmp/install-terraform.sh
    12. sudo bash /tmp/install-terraform.sh
  • バージョンの切り替え
    1. alternatives --display terraform
    2. sudo alternatives --set terraform /opt/terraform.${TERRAFORM_VER}/terraform
    3.  
    4. # 削除
    5. sudo alternatives --set terraform /opt/terraform.${TERRAFORM_VER}/terraform

不具合

  • v0.6.16: v0.6.15と同様にawsリソースを作り直す不具合有り
  • v0.6.15: awsリソースを全部作り直そうとする不具合がある。destroy, apply, refresh, planで確認
  • v0.6.14: heroku_app SSL証明書更新でクラッシュした
  • v0.4.2: awsのroot_block_deviceでvolume_size等を変更しようとすると「logicalType cannot be modified on root device」のエラーが出る。v0.3.7へダウングレードした。
  • v0.4.2: aws_instance の *_block_device が毎回違うように表示される
  • v0.4.2: aws_security_group の 自己参照、他セキュリティグループIDの指定がうまくいかない
  • v0.4.2: --target=リソース名で、ハイフン(-)に対応していない。 例:--target=aws_route53_record.web-01 はNG、web_01ならOK

インストール

  • CentOS6.x /usr/local/binにインストールする
    1. wget -O terraform_0.3.7_linux_amd64.zip https://dl.bintray.com/mitchellh/terraform/terraform_0.3.7_linux_amd64.zip
    2. sudo unzip terraform_0.3.7_linux_amd64.zip -d /usr/local/bin/
    3.  
    4. terraform version
    5. Terraform v0.3.7

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2018-08-20 (月) 18:11:19 (1d)