Terraformとループ


hokanでインターン中のかわひとです。

terraformに入門して１週間経ったので、学んだことを記事にしてみました。

ちなみにQiita初記事です！

## 複数のEC2を一括で立ち上げてみる

### for_eachを使う

```terraform:main.tf
provider "aws" {
  region = "ap-northeast-1"
}

module "ec2" {
    source = "./ec2"
    arguments = {
        "example1" = {
            instance_type = "t2.micro"
            ami = "ami-09ebacdc178ae23b7"
        },
        "example2" = {
            instance_type = "t3.nano"
            ami = "ami-0578730a012dc2185"
        },
        "example3" = {
            instance_type = "t2.nano"
            ami = "ami-0df99b3a8349462c6"
        }
    }
}

output "hello" {
  value = module.ec2
}
```

```terraform:ec2/ec2.tf
variable "arguments" {
  type = map(map(string))
}

resource "aws_instance" "ec2" {
    for_each = var.arguments
    ami = each.value.ami
    instance_type = each.value.instance_type
    tags = {
        Name = each.key
    }
}

resource "aws_eip" "eip" {
    for_each = aws_instance.ec2
    instance = each.value.id
}

output "ec2_ids" {
    value = { for k, v in aws_instance.ec2 : k => v.id }
}

```

for_eachはTerraform 0.13から追加された記法です。

公式によると

> The for_each meta-argument accepts a map or a set of strings

とありますから、`list(map(string))`とかループしようとすると怒られます。


また作成された各EC2インスタンスをループすることもできます。

上記のコードでは、各インスタンスをループして、Elastic IP アドレスを登録しています。

各EC2インスタンスへの参照は、aws_instance.ec2[key]です。keyは上記の例でいうと、"example1"や"example2"になります。

```terraform
# アウトプット
sample_output = {
  "example1" = "i-060e0264a993b95d2"
  "example2" = "i-025aa2d522d80c951"
  "example3" = "i-0c7a2dd83f5f0499e"
}
```

アウトプット部分で使っているforの記法は、公式サイトを参考にしてください。

https://www.terraform.io/docs/language/expressions/for.html


### countを使う

terraformでは、count を使えば単純に数を増やせます。

```terraform
resource "aws_instance" "ec2" {
    ami = "ami-09ebacdc178ae23b7"
    instance_type = "t2.micro"
    count = 10
}
```

同じ性能のインスタンスが10個立ち上がります。

インスタンスによって値を変えたい場合は、count.indexを利用します。

```terraform:main.tf
provider "aws" {
  region = "ap-northeast-1"
}

module "ec2" {
    source = "./ec2"
    arguments = [
        {
            name = "example1"
            instance_type = "t2.micro"
            ami = "ami-09ebacdc178ae23b7"
        },
        {
            name = "example2"
            instance_type = "t3.nano"
            ami = "ami-0578730a012dc2185"
        },
        {
            name = "example3"
            instance_type = "t2.nano"
            ami = "ami-0df99b3a8349462c6"
        }
    ]
}

output "sample_output" {
  value = module.ec2.ec2_id
}

```

```terraform:ec2/ec2.tf
variable "arguments" {
  type = list(map(string))
}

resource "aws_instance" "ec2" {
    count = length(var.arguments)
    ami = var.arguments[count.index].ami
    instance_type = var.arguments[count.index].instance_type
    tags = {
        Name = var.arguments[count.index].name
    }
}

resource "aws_eip" "eip" {
    count = length(var.arguments)
    instance = aws_instance.ec2[count.index].id
}

output "ec2_id" {
    value = aws_instance.ec2.*.id
    # * = 1, 2, 3
}
```

`count = length(var.arguments)`とすることで、リストの長さ分だけEC2インスタンスを作成します。また`count.index`を使うことで、リストの各要素を取り出しています。

for_eachではmapをループしましたが、countの構文ではリストをループする感じです。

またoutputの中では、`*`という記号でindexを使えるっぽいです。

```terraform
#　アウトプット
sample_output = [
  "i-0a45fc109254eb6ea",
  "i-0dd457447e07d00ee",
  "i-00b310944d415a592",
]
```


## セキュリティグループのルール

ネストされたブロック内でループするには、dynamicブロックを使う必要があります。

普通にfor_eachを使おうとするとエラーがでるので注意が必要です。


```terraform
variable "ingressrules" {
  type = list(number)
  default = [80, 443, 22, 25]
}

resource "aws_security_group" "sample" {
  dynamic "ingress" {
    for_each = var.ingressrules
    content {
      from_port = ingress.value
      to_port = ingress.value
      protocol = "TCP"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }
}
```

とくにセキュリティグループのインバウンド/アウトバウンドルールは、単純なループになりがちなので、この記法が活躍すると思います。

for_eachの各要素にアクセスするには、each.value ではなく、ブロック名.value とする点に注意が必要です。

なお iterator を指定すると、iterator名.value とできます。

↓dynamic ブロックの使い方はこちら。

https://www.terraform.io/docs/language/expressions/dynamic-blocks.html