Memo/Terraform/aws

https://dexlab.net:443/pukiwiki/index.php?Memo/Terraform/aws
 

AWS


AMI


s3 bucketとobjectの生成

  • terraform v0.12.12
  • ループで複数オブジェクトを作成できるが、数が増えると遅そう。遅い場合aws-cliの 「aws s3 sync」を検討した方が良い。
resource "aws_s3_bucket" "example" {
  bucket = "example-bucket"
  acl    = "private"
}

locals {
  s3_objects = [
    {
      key : "index.html"
      source : "contents/index.html"
    },
    {
      key: "top.html"
      source: "contents/top.html"
    }
  ]
}

resource "aws_s3_bucket_object" "object" {
  count  = length(local.s3_objects)
  bucket = aws_s3_bucket.example.id
  key    = local.s3_objects[count.index].key
  source = local.s3_objects[count.index].source
}

root_block_device,ebs_block_deviceで分けて処理をしたい

  • aws_instance内のvolume_tags だと、root_block_device, ebs_block_device 両方に同じタグが付く
  • ebs_block_device だけに任意のタグを付けたい場合。
    • 新規構築であれば、aws_instance 内に ebs_block_device を書かない。後から変更できなくなる。「aws_ebs_volume」「aws_volume_attachment」と分けて書いた方が良さそう。ただ、その場合「delete_on_termination」が効かないので、手動でEC2をterminateした場合、追加したEBSはそのまま残る。
  • applyする場合。commandのexit codeが0だと、2回目実行されない。「command = "...; exit 1"」とするか、destroyする。
    terraform apply -target null_resource.backup_web_ebs1
  • terraform v0.12.9: 「aws_instance.web[0].ebs_block_device[0].volume_id」がエラーになる。
resource "null_resource" "backup_web_ebs1" {
  count         = length(aws_instance.web)
  provisioner "local-exec" {
    command = <<EOD
aws ec2 create-tags \
--tags Key=backup:DailyBackup7Days,Value=${count.index == 0 ? true : false} \
--resources ${element(aws_instance.web[count.index].ebs_block_device[*].volume_id, 0)} \
--profile ${var.aws_profile} \
--region ${var.aws_region}
EOD
  }
}
  • terraform v0.11.14: 一度変数(locals)を使う必要があった。
locals {
  web_ebs1 = "${flatten(aws_instance.web.*.ebs_block_device)}"
}

resource "null_resource" "backup_web_ebs1" {
  count         = "${length(aws_instance.web.*.id)}"
  provisioner "local-exec" {
    command = <<EOD
aws ec2 create-tags \
--tags Key=backup:DailyBackup7Days,Value=${count.index == 0 ? true : false} \
--resources ${lookup(local.web_ebs1[count.index], "volume_id")} \
--profile ${var.aws_profile} \
--region ${var.aws_region}
EOD
  }
}

aws_instance 内の ebs_block_device の volume_id の参照

  • ouput部分を更新する場合。「terraform ouput」では新しくouputを追加したり、値が変わった場合でも変わらないので注意。
    terraform refresh -target aws_instance.web
  • terraform v0.12.9:
output "ebs_test01" {
  value = "${aws_instance.web[0].ebs_block_device}"
}

# 結果
ebs_test01 = [
 {
   "delete_on_termination" = true
   "device_name" = "/dev/sdf"
   "encrypted" = false
   "iops" = 100
   "kms_key_id" = ""
   "snapshot_id" = ""
   "volume_id" = "vol-0123456789abcdef"
   "volume_size" = 5
   "volume_type" = "gp2"
 },
]

output "ebs_test02" {
  value = "${aws_instance.web[0].ebs_block_device[0].volume_id}"
}

# 結果: エラー
# Block type "ebs_block_device" is represented by a set of objects, and set
# elements do not have addressable keys. To find elements matching specific
# criteria, use a "for" expression with an "if" clause.

output "ebs_test03" {
  value = "${element(aws_instance.web[0].ebs_block_device[*].volume_id, 0)}"
}

# 結果
ebs_test03 = vol-0123456789abcdef
  • terraform v0.11.14: 構文解析がおかしい。element()がlist[map{}]構造だとエラーになる。
output "ebs_test01" {
  value = "${aws_instance.web.*.ebs_block_device}"
}

# result
ebs_test01 = [
    [
        map[delete_on_termination:1 device_name:/dev/sdf encrypted:1 iops:100 snapshot_id: volume_id:vol-0123456789abcdef volume_size:5 volume_type:gp2]
    ],
    [
        map[delete_on_termination:1 device_name:/dev/sdf encrypted:1 iops:100 snapshot_id: volume_id:vol-0f50829f1dfbc02ca volume_size:5 volume_type:gp2]
    ]
]

output "ebs_test02" {
  value = "${lookup(aws_instance.web.0.ebs_block_device[0], "volume_id")}"
}

# result
ebs_test02 = vol-0123456789abcdef

output "ebs_test04" {
  value = "${slice(flatten(aws_instance.web.*.ebs_block_device[0]), 0, 1)}"
}

# result
ebs_test04 = [
    {
        delete_on_termination = 1,
        device_name = /dev/sdf,
        encrypted = 1,
        iops = 100,
        snapshot_id = ,
        volume_id = vol-0123456789abcdef,
        volume_size = 5,
        volume_type = gp2
    }
]

ACM


data.aws_instance: 既存EC2の参照

  • AWS: aws_instance - Terraform by HashiCorp
    • v0.12.9現在、data一つにつき、EC2は1台のみ対応。
    • 複数のEC2が一致すると、「Error: Your query returned more than one result. Please try a more specific search criteria.」で失敗する。

EC2を作る時は、リソース毎に分ける

AWS上のIDが分かれているリソース(EC2, EBS, SecurityGroup)を作る場合、ID毎にリソースを分けて作成する事で保守性が上がる。

  • aws_instance
    • ebs_block_device をこのリソース内で定義できるが、apply後、EBSを追加しようとinstanceごと破棄 -> 再生成しようとするので保守性が悪い。

SNSメッセージをSlackへ通知

  • localでのテスト。slackへ送信される。
    git clone https://github.com/builtinnya/aws-sns-slack-terraform.git
    cd aws-sns-slack-terraform/
    pip install requests --upgrade
    
    export WEBHOOK_URL="hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX"
    export CHANNEL_MAP=`echo '{ "production-notices": "#webhook-tests" }' | base64`
    python sns-to-slack/lambda_function.py
    
    # jqでjsonを整形して見たい場合。
    | grep "DEBUG EVENT:" | cut -c 14- | jq .
  • LambdaのDEBUGログ等は、CloudWatch logs > ロググループ > /aws/lambda/sns-to-slack のストリームに出力される。
  • lambda_function.py の中にテスト用jsonが埋め込まれている。
    • sns_event_template の ['Records'][0]['Sns']['Message'] 部分。「DEBUG EVENT:」はこの部分が出力される。
    • slackへ送信される部分は DEBUG PAYLOAD: としてCloudWatch logsへ記録される


Lambda


CloudFront


WAF


policy jsonの代わりに aws_iam_policy_document を使う

terraformのドキュメントの例には「policy = <json>」のように書いてある部分もあるが、毎回差分が出る不具合があるので、aws_iam_policy_documentを使うと良さそう。

  • s3.tf: exampleバケットに対して、Webhosting用のPublic Read、IP制限をする場合
    resource "aws_s3_bucket_policy" "example" {
        bucket = "${aws_s3_bucket.example.id}"
        policy = "${data.aws_iam_policy_document.s3_example_public_read.json}"
    }
    
    data "aws_iam_policy_document" "s3_example_public_read" {
      statement {
        sid = "IPAllow"
        effect = "Allow"
    
        principals {
          type = "*"
          identifiers = ["*"]
        }
    
        actions = [
          "s3:GetObject",
        ]
    
        resources = [
          "arn:aws:s3:::example/*",
        ]
    
        condition {
          test     = "IpAddress"
          variable = "aws:SourceIp"
          values = [
            "192.0.2.0/24",   # TEST-NET-1
            "198.51.100.0/24",# TEST-NET-2
            "203.0.113.0/24", # TEST-NET-3
          ]
        }
      }
    }

既存EC2にIAM roleを付けたい

  • 環境
    • Terraform v0.10.7
  • iam_instance_profileを付けて、terraform applyしても反映されない。
  • aws cliのassociate-iam-instance-profile だと可能
    aws ec2 associate-iam-instance-profile \
    --instance-id i-123456 \
    --iam-instance-profile "Name=example-role" \
    --profile example
    }

セキュリティグループルールはaws_security_group_ruleを使う

ルールの追加は aws_security_group_rule 推奨。 aws_security_groupにもruleは書けるが、plan時に順番が変わって、毎回diffとして表示されてしまう。diffも見難い。


No valid credential sources found for AWS Provider.

  • チェックポイント
    • terraformは「~/.aws/credentials」内のkeyしか読まない。このファイル内にクレデンシャルが必要。ansibleは「~/.aws/config」にkeyがあれば読んでくれるので誤解する。
      cat ~/.aws/credentials
      [myprofile]
      aws_access_key_id = AK****
      aws_secret_access_key = ****
    • ちょっとしたフォーマットの違い(ダブルクオートの有無、スペースの有無等)で読めていない。一度セクションを消して、awsコマンドで生成する。
      aws configure --profile myprofile

tfファイル内で複数AWSアカウント、複数リージョンを扱う

  • tfファイルで、異なるリージョンを扱う。デフォルトproviderは必須のようで、存在しないとエラーになる(terraform v0.10.2)
    variable "aws_profile" {} # awscli profile name. terraform plan/apply時にプロンプトを出す
    provider "aws" {
      profile = "${var.aws_profile}"
      region  = "ap-northeast-1" # tokyo
    }
    
    provider "aws" {
      profile = "${var.aws_profile}"
      region  = "us-east-1"
      alias   = "us-east-1"
    }
    
    resource "aws_s3_bucket" "tokyo-example" {
      bucket = "tokyo.example.com"
      acl = "private"
    }
    
    resource "aws_s3_bucket" "us-east-1-example" {
      provider = "aws.us-east-1"
      bucket = "us-east-1.example.com"
      acl = "private"
    }
  • 作成済みリソースを terraform importする場合
    terraform import aws_s3_bucket.tokyo-example tokyo.example.com
    terraform import -provider=aws.us-east-1 aws_s3_bucket.us-east-1-example virginia.example.com

VPC Peering

  • 異なるAWSアカウント間で、VPC peeringを設定する
    • アクセプタ側でVPC > ピア接続 > 選択 > アクション > 許可する
    • 手動で許可する場合、terraform applyが必ず失敗する(auto_accept = false でも)。許可した後、もう一度実行が必要
  • aws.tf
    variable "aws_region" { default = "ap-northeast-1" }
    // リクエスタ側のAWS account情報. aws configure --profile <name> で設定しておく
    variable "main_aws_profile" {}
    variable "main_aws_vpc_id" { default = "vpc-aaaaa" }
    variable "main_aws_route_table_id" { default = "rtb-11111111" }
    variable "main_aws_cidr" { default = "172.31.0.0/16" }
    provider "aws" {
      profile = "${var.main_aws_profile}"
      region  = "${var.aws_region}"
    }
    
    // "${data.aws_caller_identity.main.account_id}" として参照可能
    data "aws_caller_identity" "main" {
    }
    
    // アクセプタ側のAWS account情報
    variable "peer_aws_account_id" { default = "2222222222" }
    variable "peer_vpc_id" { default = "vpc-bbbbb" }
    variable "peer_vpc_cidr" { default = "10.5.0.0/16" }
  • iam.tf
    output "aws_iam_role.vpc_peering.arn"     { value = "${aws_iam_role.vpc_peering.arn}" }
    resource "aws_iam_role" "vpc_peering" {
        name               = "main-vpcpeering"
        assume_role_policy = "${data.aws_iam_policy_document.sts-assumerole.json}"
    }
    
    # AWS console上だとIAM > ロール > ロール名 > 信頼関係タブの部分
    data "aws_iam_policy_document" "sts-assumerole" {
      statement {
        actions = [
          "sts:AssumeRole",
        ]
        effect = "Allow"
        principals = {
          type = "AWS"
          identifiers = [
            "arn:aws:iam::${var.peer_aws_account_id}:root",
          ]
        }
      }
    }
    
    # AWS console上だとIAM > ロール > ロール名 > アクセス許可タブ > インラインポリシーの部分
    resource "aws_iam_role_policy" "vpc_peering" {
        name   = "main-vpcpeering"
        role   = "${aws_iam_role.vpc_peering.id}"
        policy = "${data.template_file.vpc_peering.rendered}"
    }
    
    data "template_file" "vpc_peering" {
      template = "${file("./policy/vpc-peering-connection.tpl")}"
    
      vars {
        aws_region          = "${var.aws_region}"
        main_aws_account_id = "${data.aws_caller_identity.main.account_id}"
        main_aws_vpc_id     = "${var.main_aws_vpc_id}"
        peer_aws_account_id = "${var.peer_aws_account_id}"
        peer_aws_vpc_id     = "${var.peer_vpc_id}"
      }
    }
  • ./policy/vpc-peering-connection.tpl
    {
       "Version":"2012-10-17",
       "Statement":[
          {
             "Effect":"Allow",
             "Action":"ec2:AcceptVpcPeeringConnection",
             "Resource":"arn:aws:ec2:${aws_region}:${main_aws_account_id}:vpc-peering-connection/*",
             "Condition":{
                "ArnEquals":{
                   "ec2:RequesterVpc":"arn:aws:ec2:${aws_region}:${peer_aws_account_id}:vpc/${peer_aws_vpc_id}"
                }
             }
          },
          {
             "Effect":"Allow",
             "Action":"ec2:AcceptVpcPeeringConnection",
             "Resource":"arn:aws:ec2:${aws_region}:${main_aws_account_id}:vpc/${main_aws_vpc_id}"
          }
       ]
    }
  • vpc_peering.tf
    // Requester's side of the connection.
    // https://www.terraform.io/docs/providers/aws/r/vpc_peering.html
    resource "aws_vpc_peering_connection" "main" {
      vpc_id        = "${var.main_aws_vpc_id}"
      peer_vpc_id   = "${var.peer_vpc_id}"
      peer_owner_id = "${var.peer_aws_account_id}"
      auto_accept   = false
    
      tags {
        Name = "VPC Peering between main and peer"
        Side = "Requester"
      }
    }
    
    resource "aws_route" "main" {
      route_table_id            = "${var.main_aws_route_table_id}"
      destination_cidr_block    = "${var.peer_vpc_cidr}"
      vpc_peering_connection_id = "${aws_vpc_peering_connection.main.id}"
    }
  • アクセプタ側のAWSアカウントも自分で管理している時
    variable "peer_aws_route_table_id" { default = "rtb-22222" }
    // Accepter's credentials.
    
    provider "aws" {
      alias = "peer"
      profile = "peer" // aws configure --profile <name> で設定しておく
      region = "ap-northeast-1"
    }
    
    // Accepter's side of the connection.
    // https://www.terraform.io/docs/providers/aws/r/vpc_peering_accepter.html
    resource "aws_vpc_peering_connection_accepter" "peer" {
      provider                  = "aws.peer"
      vpc_peering_connection_id = "${aws_vpc_peering_connection.main.id}"
      auto_accept               = true
    
      tags {
        Name = "VPC Peering between main and peer"
        Side = "Accepter"
      }
    }
    
    resource "aws_route" "peer" {
      provider                  = "aws.peer"
      route_table_id            = "${var.peer_aws_route_table_id}"
      destination_cidr_block    = "${var.main_aws_cidr}"
      vpc_peering_connection_id = "${aws_vpc_peering_connection_accepter.peer.id}"
    }

AWS account_id/arn/user_idの取得

// "${data.aws_caller_identity.main.account_id}" として参照できる
data "aws_caller_identity" "main" {}

EC2起動時にuser_dataを渡す

  • ec2.tf
    resource "aws_instance" "web01" {
    ...
      user_data = "${file("cloud-config.yaml")}"
    ...
    }

セキュリティグループを後から変更しようとするとEC2を作り直してしまう

  • VPCの場合、security_groups ではなく vpc_security_group_ids を使う

aws_nat_gateway

  • subnet毎にnat-gatewayを作成
  • terraform v0.10.0
    variable aws_subnets {
      type = "list"
      default = [
        "subnet-aaaa",  # AZ: ap-northeast-1a
        "subnet-cccc",  # AZ: ap-northeast-1c
      ]
    }
    
    resource "aws_nat_gateway" "gw" {
      allocation_id = "${aws_eip.gw.*.id[count.index]}"
      subnet_id     = "${var.aws_subnets[count.index]}"
      count         = "${length(var.aws_subnets)}"
    }
    
    resource "aws_eip" "gw" {
      vpc   = true
      count = "${length(var.aws_subnets)}"
    }

複数リソースの指定

  • EC2の数がsubnet数を超えても、AZが正しく振られるようにする
variable "ec2_subnets" { default = ["subnet-xxxx","subnet-xxxx","subnet-xxxx"] }

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

RDS

AWS: aws_db_instance - Terraform by HashiCorp

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

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2019-11-06 (水) 17:25:56 (10d)