Memo/Terraform

https://dexlab.net:443/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以上が作成されており、不具合の修正が間に合っていない印象
    • 既存リソースの管理はコードを書いて、リソースID毎にimportコマンドの実行が必要なので大変。import非対応のリソースもある。
    • 状態をステートファイル(terraform.tfstate)に持ち、これを元に差分更新する。
      • 無くすと大変。更新ができなくなる。(既存リソースのimportは出来るが、数が多いと大変)
      • ステートファイルは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を作り直そうとする

無停止での更新・デプロイ/Rolling Update

戦略:

  • 新しいインスタンスを追加して、古いインスタンスは削除。terraform自体、updateが苦手に見える。

複雑な変数の定義と参照

  • terraform v0.11.x ではmapにlistを入れたりできないため、複雑な変数を定義する場合、複数のmapを組み合わせて、lookup()等で参照する方法がある。
  • yamlだとこんな感じの変数をterraformで定義したい。
    tables:
      key01:
        attr01: 10
      key02:
        attr01: 20
    • example.tf: terraformでは複数リソースはcountでループするため、indexをわざわざ定義している
      variable "tables" {
        default = {
          "0" = "key01"
          "1" = "key02"
        }
      }
      
      output "tables.count" { value = "${length(var.tables)}" } # key01
      output "tables.key0" { value = "${lookup(var.tables, 0)}" } # 2
      
      
      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

三項演算子/条件によりリソースの作成を制御する

  • v0.8.11現在、if文は無い。
  • 「<resource>.count」 はドキュメントに無くても設定できる。「count=0」にするとリソースは生成されない。
  • "var.something"がTRUEの時に、1台のインスタンスを立てる
    resource "aws_instance" "vpn" {
      count = "${var.something ? 1 : 0}"
    }
  • 指定S3 bucektのreadonly用roleを作成。"var.s3_r_bucket_arns" が空の時にはpolicyを作成しない。 aws_iam_role_policy.countはドキュメントに無いが設定できる
    # 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)}/*" )}"]
      }
    }

fmt: 自動整形

  • カレントディレクトリの全ファイルを自動整形する
    terraform fmt

複数個リソースの参照

  • terraformのいつのバージョンからか以下のような2つ以上のリソースの場合「.1.」の記述がエラー(not found for variable)になった。
    output "web-0001.public_dns"     { value = "${aws_instance.web.1.public_dns}" }
  • outputは'*'で複数リソースをJSONで出力可能:
    output "web.public_dns"     { value = "${aws_instance.web.*.public_dns}" }
  • 値を明示的に参照する場合
    "${element(aws_instance.web.*.id, 1)}"

workspace: stateを分ける

stateはbackend指定でS3に置けるが、通常default一つしか無い。
dev, stg, prodのように分けてstateを管理できる。
awsのregion毎に分けても良い。

  • dev
    terraform workspace new dev
    terraform workspace select dev
  • "${terraform.workspace}"で現在のworkspace名の参照が出来る

locals: ローカル変数

  • NG: variableのdefaultで別変数を参照すると「default may not contain interpolations」エラーになる
    variable "cidr_whitelist" {
      type = "list"
      default = [
        { "value" = "${var.cidr_office}" type = "IPV4" },
      ]
  • OK: locals は別変数の参照が可能
    locals {
      cidr_whitelist = [
        { "value" = "${var.cidr_office}" type = "IPV4" },
      ]
    }

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


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

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

  • 元変数: ["arn:aws:s3:::example1","arn:aws:s3:::example2"]
  • 求める出力: ["arn:aws:s3:::example1/*","arn:aws:s3:::example2/*"]
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)}/*" )}"]
  }
}

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

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


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

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


タイムアウトの延長

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

  • Timeouts
  • aws_db_instanceの場合
    resource "aws_db_instance" "timeout_example" {
      name              = "mydb"
      # ...
      timeouts {
        create = "60m"
        delete = "2h"
      }
    }

alicloud(aliyun alibaba)

中国のalicloud用プロバイダー


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

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

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

  • type = "map" は省略できる
  • aws.tf
    # 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"
      }
    }
    
    output "aws_ami_ids" { value = "${var.aws_ami_ids}" }
    output "aws_ami_id_centos7" { value = "${lookup(var.aws_ami_ids, "ap-northeast-1_centos7_2017-10-12")}" }
  • 実行結果
    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

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

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

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

変数

  • 機密情報をtfファイルに書かないようにしたい場合。「-var "key=value"」が複数使える。ただし、パスワードをこの方法にしてしまうと、apply時に毎回同じパスワードを入力するハメになるのでお勧めしない。
    cat var.tf
    --
    variable api_key {}
    --
    api_key=***
    terraform plan -var "api_key=$api_key"
  • 別ファイルにしたい場合: 「-var-file="secret.tfvars"」
  • パスワード等、ランダム文字列で良い場合は生成できる。

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


VPCの作成


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

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

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

デバッグ

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

output: 出力だけを見る

public dns, IP等、後で参照したい情報を出力するのに便利。
applyした後でしか表示されない。(tfstateファイルの中身を表示しているのでは)

terraform output

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

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

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

backend: S3等にtfstateファイルを置く

  • terraform.tf:
    • このファイル内で変数は使えなかった。「configuration cannot contain interpolations」エラー
      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を置ける
        }
      }
  • 実行: terraform v0.9.3
    aws --profile myprofile configure
    
    # ~/.aws/credentials に該当キーが出来ているのが重要。
    
    terraform init
    
    terraform plan

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

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

変数で配列が扱えない

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

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

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

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

  • CentOS6.xの場合。ruby 2.1.0以上なので、rbenvやrvmで新しいrubyを使えるようにする
    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

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

  • バージョンによって動作しない機能があり、古いバージョンに切り替えたい時がある。
    • 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の場合
    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

不具合

  • 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にインストールする
    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

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2018-12-13 (木) 12:28:13 (3d)