TerraformでEC2、セキュリティグループを作成する

# はじめに

以下記事の続きです。
[TerraformでVPC、サブネット、インターネットゲートウェイを作成する](https://qiita.com/gogo-muscle/items/58fee5760400ff1bc602)

今回はEC2とセキュリティグループを作成します。
以下ディレクトリ構造です。

~~~shell
.
├── README.md
├── main.tf
├── modules
│   ├── alb
│   │   ├── Create-alb.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   ├── ec2　　　　　　　　＃本記事で作成
│   │   ├── Create-ec2.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   ├── rds
│   │   ├── Create-rds.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   ├── security_group　　＃本記事で作成
│   │   ├── Create-securitygroup.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   └── vpc ＃前回作成
│       ├── Create-vpc.tf
│       ├── outputs.tf
│       └── variables.tf
├── outputs.tf
├── terraform.tfstate
├── terraform.tfstate.backup
└── variables.tf
~~~

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

セキュリティグループもModule化するため、`main.tf`に追記します。
`sg_config`は環境に合わせてください。
VPCは、前回作成したVPCモジュールと紐づけています。

~~~terraform:./main.tf

module ec2-sg {
  source = "./modules/security_group"

  sg_config = {
    name        = "ec2-sg"
    vpc_id      = module.vpc.vpc_id
    protocol    = "tcp"
    port        = [22, 80, 443]
    cidr_blocks = ["0.0.0.0/0"]
  }
~~~

次に`module ec2-sg`で定義した変数をinputします。
`object`で取り込むと変数1つずつ`variable`でinputする必要が無いので便利です。

~~~terraform:./modules/security_group/variables.tf
variable "sg_config" {
  type = object({
    name        = string
    vpc_id      = string
    protocol    = string
    port        = list(number)
    cidr_blocks = list(string)
  })
}
~~~


セキュリティグループ全体の設定を書きます。`for_each = var.sg_config.port`でport[22,80,443]でループを回しています。

~~~terraform:./modules/security_group/Create-securitygroup.tf
resource "aws_security_group" "mysg" {
  name   = var.sg_config.name
  vpc_id = var.sg_config.vpc_id

  dynamic "ingress" {
    for_each = var.sg_config.port

    content {
      from_port   = ingress.value
      to_port     = ingress.value
      protocol    = var.sg_config.protocol
      cidr_blocks = var.sg_config.cidr_blocks
    }
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
~~~

後ほどEC2に紐付けるために、セキュリティグループIDをoutputしておきます。

~~~terraform:./modules/security_group/outputs.tf
output "sg_id" {
  value = aws_security_group.mysg.id
}
~~~

## EC2の作成

EC2もmodule化するため`main.tf`に追記します。ちょっとパラメータが多くなってしまいました。

~~~terraform:./main.tf
module ec2 {
  source = "./modules/ec2"

  ec2_config = {
    vpc_id           = module.vpc.vpc_id
    public_subnet_id = module.vpc.public_subnet_id
    sg_id            = module.ec2-sg.sg_id
    NameTag          = "Terraform"
    instance_type    = "t2.micro"
    instance_count   = 2
    key_name         = "test_key"
  }
  strage_config = {
    volume_type           = "gp2"
    volume_size           = 8
    delete_on_termination = true
  }
}
~~~

変数にinputします。

~~~terraform:./modules/ec2/variables.tf
variable "ec2_config" {
  type = object({
    vpc_id           = string
    public_subnet_id = list(string)
    sg_id            = string
    NameTag          = string
    instance_type    = string
    instance_count   = number
    key_name         = string
  })
}

variable "strage_config" {
  type = object({
    volume_type           = string
    volume_size           = string
    delete_on_termination = bool
  })
}
~~~

EC2作成のコードを書きます。
いつか`count`ではなくインスタンス数で`for_each`に書き直します。。

~~~terraform:./modules/ec2/Create-ec2.tf
# 最新のAmazon Linux AMI IDを取得
data aws_ssm_parameter amzn2_ami {
  name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
}

resource "aws_instance" "Myinstance" {
  count         = var.ec2_config.instance_count
  ami           = data.aws_ssm_parameter.amzn2_ami.value
  instance_type = var.ec2_config.instance_type
  key_name      = var.ec2_config.key_name
  vpc_security_group_ids = [
    var.ec2_config.sg_id
  ]
  subnet_id = element(var.ec2_config.public_subnet_id, count.index % length(var.ec2_config.public_subnet_id))
  root_block_device {
    volume_type           = var.strage_config.volume_type
    volume_size           = var.strage_config.volume_size
    delete_on_termination = var.strage_config.delete_on_termination
  }
  tags = {
    Name = "${var.ec2_config.NameTag}-instance${count.index}"
  }
}
~~~

作成したEC2インスタンスのpublic_ipをターミナル上に表示するためにoutputします。
DNS名を表示したい場合は、末尾を`public_dns`にします。
インスタンスIDはALBとの接続で使います。

~~~terraform:./modules/ec2/outputs.tf
output "instance_ip" {
  value = {
    for instance in aws_instance.Myinstance[*] :
    instance.tags.Name => instance.public_ip
  }
}
output "instance_id" {
  value = aws_instance.Myinstance[*].id
}
~~~

実は上記だけだとターミナル上で表示されないのでterraformルートディレクトリに設置した`outputs.tf`にも同じようなことを書きます。

~~~terraform:./outputs.tf
output "instance_ip" {
  value = module.ec2.instance_ip
}
~~~

以上でEC2作成コードが完成です。
実行後はこんな感じで表示されます。（２台作成時）

~~~shell
% terraform plan
% terraform apply

Outputs:

instance_ip = {
  "Terraform-instance0" = "13.113.167.40"
  "Terraform-instance1" = "13.115.52.183"
}
~~~

SSHも出来ました。

~~~shell
% ssh -i ~/.ssh/test-key.pem ec2-user@13.113.167.40
~~~

これでVPCとEC2が作成出来ました！次回はALBを作成します。
（再度セキュリティグループのモジュール使います）

# 参考

* [TERRAFORMのMODULEを作成する(VPC)](https://nezumisannn.io/blog/terraform-module-vpc/)
* [Terraform 0.11→0.12で追加された新機能](https://dev.classmethod.jp/articles/terraform-0-12-new-features/)